C++序列容器存储智能指针详解
通常用容器保存指针比保存对象更好,而且大多数时候,保存智能指针比原生指针好。下面是一些原因:在容器中保存指针需要复制指针而不是它所指向的对象。复制指针通常比复制对象快。在容器中保存指针可以得到多态性。存放元素基类指针的容器也可以保存其派生类型的指针。当要处理有共同基类的任意对象序 列时,这种功能是非常有用的。应用这一特性的一个常见示例是展示一个含有直线、曲线和几何形状的对象序列。对指针容器...
通常用容器保存指针比保存对象更好,而且大多数时候,保存智能指针比原生指针好。下面是一些原因:
- 在容器中保存指针需要复制指针而不是它所指向的对象。复制指针通常比复制对象快。
- 在容器中保存指针可以得到多态性。存放元素基类指针的容器也可以保存其派生类型的指针。当要处理有共同基类的任意对象序 列时,这种功能是非常有用的。应用这一特性的一个常见示例是展示一个含有直线、曲线和几何形状的对象序列。
- 对指针容器的内容进行排序的速度要比对对象排序快;因为只需要移动指针,不需要移动对象。
- 保存智能指针要比保存原生指针安全,因为在对象不再被引用时,自由存储区的对象会被自动删除。这样就不会产生内存泄漏。不指向任何对象的指针默认为 nullptr。
如你所知,主要有两种类型的智能指针:unique_ptr<T>
和 shared_ptr<T>
,其中 unique_ptr<T>
独占它所指向对象的所有权,而 shared_ptr<T>
允许多个指针指向同一个对象。还有weak_ptr<T>
类型,它是一类从 shared_ptr<T>
生成的智能指针,可以避免使用 shared_ptrs<T>
带来的循环引用问题。unique_ptr<T>
类型的指针可以通过移动的方式保存到容器中。例如,下面的代码可以通过编译:
std::vector<std::unique_ptr<std::string>> words;
words.push_back(std::make_unique<std::string>("one"));
words.push_back(std::make_unique<std::string>("two"));
vector 保存了 unique_ptr<string>
类型的智能指针。make_unique<T>()
函数可以生成对象和智能指针,并且返回后者。因为返回结果是一个临时 unique_ptr<string>
对象,这里调用一个有右值引用参数的push_back()
函数,因此不需要拷贝对象。另一种添加 unique_ptr
对象的方法是,先创建一个局部变量 unique_ptr
,然后使用std::move()
将它移到容器中。然而,后面任何关于拷贝容器元素的操作都会失败,因为只能有一个 unique_ptr
对象。如果想能够复制元素,需要使用 shared_ptr
对象;否则就使用 unique_ptr
对象。
在序列容器中保存指针
下面首先解释一些在容器中使用原生指针会碰到的问题,然后再使用智能指针(这是推荐的使用方式)。下面是一段代码,用来从标准输入流读取单词,然后将指向自由存储区的字符串对象的指针保存到 vector 容器中:
std::vector<std::string*> words;
std::string word;
std::cout << "Enter words separated by spaces, enter Ctrl+Z on a separate line to end: \n";
while (true)
{
if ((std::cin >> word).eof())
{
std::cin. clear();
break;
}
words.push_back(new std::string {word});// Create object and store its address
}
push_back()
的参数表达式在自由存储区生成了一个字符串对象,因此 push_back() 的参数是一个对象的地址。可以按如下方式输出 words
中的内容:
for (auto& w : words)
std: : cout << *w <<" ";
std::cout << std::endl;
如果想使用迭代器来访问容器中的元素,输出字符串的代码可以这样写:
for (auto iter = std::begin(words);iter != std::end(words); ++iter)
std::cout << **iter <<" ";
std::cout << std::endl;
注意,在删除元素时,需要先释放它所指向的内存。如果不这样做,在删除指针后,就无法释放它所指向的内存,除非保存了指针的副本。这是容器中的原生指针常见的内存泄漏来源。无论什么时候删除一个是原生指针的元素,都需要首先释放它所指向的内存:
for (auto iter = std::begin(words); iter != std::end(words);)
{
if (**iter == "one")
{
delete *iter;//Release the memory...
words.erase (iter); //... then delete the pointer
}
else
++iter;
}
在离开vector
的使用范围之前,记住要删除自由存储区的string
对象。可以按如下方式来实现:
for (auto& w : words)
delete w; // Delete the string pointed to
words.clear(); // Delete all the elements from the vector
用索引来访问指针,这样就可以使用 delete
运算符删除string
对象。当循环结束时,vector
中的所有指针元素都会失效,因此不要让vector
处于这种状态。调用 clear() 移除所有元素,这样
size() `会返回 0。当然,也可以像下面这样使用迭代器:
for (auto iter = std::begin(words);iter != std::end(words); ++iter)
delete *iter;
如果保存了智能指针,就不用担心要去释放自由存储区的内存。智能指针会做这些事情。下面是一个读入字符串,然后把 shared_ptr<string>
保存到 vector 中的代码片段:
std::vector<std::shared_ptr<std::string>> words; std::string word;
std::cout << "Enter words separated by spaces, enter Ctrl+Z on a separate line to end:\n";
while (true)
{
if ((std::cin >> word).eof())
{
std::cin. clear ();
break;
}
words.push_back(std::make_shared<string>(word)); // Create smart pointer to string & store it
}
这和使用原生指针的版本没有什么不同。vector
模板现在的类型参数是std::shared_ptr<std::string>
,push_back()
的参数会调用 make_shared()
,在自由存储区生成 string
对象和一个指向它的智能指针。因为智能指针由参数表达式生成,这里会调用一个右值引用参数版的push_back()
来将指针移到容器中。
模板类型参数可能有些冗长,但是可以使用 using 来简化代码。例如:
using PString = std::shared_ptr<std::string>;
使用 using 后,可以这样定义:
std::vector<PString> words;
可以通过智能指针元素来访问字符串,这和使用原生指针相同。前面那些输出 words 内容的代码片段都可以使用智能指针。当然,不需要删除自由存储区的 string 对象;因为智能指针会做这些事情。执行 words.clear() 会移除全部的元素,因此会调用智能指针的析构函数;这也会导致智能指针释放它们所指向对象的内存。
为了阻止 vector 太频繁地分配额外内存,可以先创建 vector,然后调用 reserve() 来分配一定数量的初始内存。例如:
std::vector<std::shared_ptr<std::>>words;
words.reserve(100); // Space for 100 smart pointers
这样生成 vector 比指定元素个数来生成要好,因为每一个元素都是通过调用 shared_ptr<string>
构造函数生成的。不这样做也不是什么大问题,但会产生一些不必要的额外开销,即使开销很小。通常,每个智能指针所需要的空间远小于它们所指向对象需要的空间,因此可以大方地使用 reserve() 来分配空间。
可以在外面使用保存的 shared_ptr<T>
对象的副本。如果不需要这种功能,应该使用 unique_ptr<T>
对象。下面展示如何在 words 中这样使用:
std::vector<std::unique_ptr<std::string>>words;
std::string word;
std::cout << "Enter words separated by spaces, enter Ctrl+Z on a separate line to end:\n";
while (true)
{
if ((std::cin >> word).eof())
{
std::cin.clear();
break;
}
words.push_back(std::make_unique<string>(word));
//Create smart pointer to string & store it
}
在上面的代码中,用 unique
代替shared
是没有差别的。
更多推荐
所有评论(0)