0%

C++11新特性 - 容器与拥有指针属性的类

本文主要介绍 C++ 中使用容器和拥有指针作为属性的类容易遇到的问题,以及 C++11 新的解决思路

在 C++03 中使用带指针属性的类

假如我们定义了这样一个类

1
2
3
4
5
6
class Person {
public:
Person(const string& name) {pName = new string(name);}
~Person() {delete pName;}
string *pName;
};

我们希望把这个类放到 vector 里面,于是写出了一段看上去似乎没问题,但事实上会崩溃的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
vector<Person> persons;

/*
这里会发生三件事情:
1. 构造一个 pName 指向 "Bob" 的 Person 对象
2. 讲这个对象浅拷贝到 persons[0] 位置
3. 析构生成 1 中生成的 Person 对象 ("Bob" 被释放)
*/
persons.push_back(Person("Bob"));

return 0;
} // persons 析构,已被释放的 "Bob" 再次被释放,程序崩溃

为了避免这个问题,在 C++03 中,常见的解决方法是给 Person 添加一个拷贝构造函数

1
Person(const Person& rhs) {pName = new string(*rhs.pName);}

然而,使用这种方法时,”Bob” 被多余地构造和释放了一次,造成了浪费

C++11 的解决方法

C++11 中,解决这个问题最简单的方法是,使用 emplace_back 替换 push_back,让容器自己来构造对象

1
2
3
4
5
6
7
8
int main() {
vector<Person> persons;

// 在 persons 内部生成 Bob
persons.emplace_back("Bob");

return 0;
}

C++11 中,各种容器的插入,都实现了 emplace 版本

然而,如果我们一定要使用 push_back,注意到 Person("Bob") 是个右值,由于 C++11 重载了右值版本的 push_back(T&&),我们给 Person 添加移动构造函数

1
Person(Person&& rhs) {pName = rhs.pName; rhs.pName = nullptr;}

这样,push_back 内部只会发生两次指针赋值操作,而不是重新构造一个字符串

那么,使用智能指针能不能解决这个问题呢?答案是可以的,两种解决方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 使用 shared_ptr
class Person {
public:
Person(const string& name):pName(new string(name)) {}
shared_ptr<string> pName;
};

// 2. 使用 unique_ptr
class Person {
public:
Person(const string& name):pName(new string(name)) {}
unique_ptr<string> pName;
};

要注意的是, 如果使用 shared_ptr,每次拷贝之后 pName 指向的字符串事实上是共享的;如果使用 unique_ptr,则 Person 是不可拷贝只可移动的