《The Boost C++ Libraries》 第一章 智能指针

Boost.SmartPointers中提供了多种智能指针,它们采用在智能指针析构时释放内存的方式,帮助管理动态分配的对象。由于析构函数在智能指针生命周期结束时被执行,所以由它管理的动态分配对象可以保证被释放。这样则不会出现内存泄漏,即使你忘记了手动delete。

从C++98开始,标准库中开始提供智能指针std::auto_ptr,但是std::auto_ptr在C++11中被废弃。C++11标准库中引入了更好的智能指针。std::shared_ptr和std::weak_ptr源于Boost.SmartPoints的boost::shared_ptr和boost::weak_ptr。boost中没有std::unique_ptr的对应实现。但是Boost.SmartPointers提供了额外四种标准库中没有的智能指针:boost::scope_ptr,boost::scoped_array,boost::shared_array和boost::intrusive_ptr。

独占所有权

boost::scope_ptr是一种独占某个动态分配对象的智能指针。boost::scope_ptr不可被拷贝或者转移。该智能指针被定义在头文件boost/scoped_ptr.hpp。

Example 1.1. 使用boost::scoped_ptr

#include <boost/scoped_ptr.hpp>
#include <iostream>

int main()
{
  boost::scoped_ptr<int> p{new int{1}};
  std::cout << *p << '\n';
  p.reset(new int{2});
  std::cout << *p.get() << '\n';
  p.reset();
  std::cout << std::boolalpha << static_cast<bool>(p) << '\n';
}

boost::scoped_ptr类型的智能指针不可以转移它所管理的对象的所有权。boost::scoped_ptr在用一个地址初始化后,动态分配的对象将在scoped_ptr被析构时或者在调用reset()方法时被释放。

Example1.1 使用了一个boost::scoped_ptr类型的智能指针p,p用一个指向动态分配的数字1的指针来初始化。通过*操作符,p被解引用并且1被打印到标准输出。

可以使用reset()方法将一个新的地址存入智能指针,例子中使用reset()方法向p传入了一个新的动态分配的数字2,原本存储于p的对象会自动被释放。

get()方法返回智能指针中存储的对象的裸地址,例子中对get()返回的地址进行解引用,并将得到的2打印至标准输出。

boost::scoped_ptr重载了bool操作符。当智能指针存储了某个对象的引用时(也即不为空),bool操作符返回true。在例子中,标准输出将输出false,因为p调用reset()被清空了。

boost::scoped_ptr的析构函数使用delete释放它引用的对象,而动态分配的数组应该用delete[]来释放,所以boost::scoped_ptr不可以用动态分配的数组地址来初始化。对于数组,Boost.SmartPointers提供了boost::scoped_array类。

Example 1.2. 使用boost::scoped_array

#include <boost/scoped_array.hpp>

int main()
{
  boost::scoped_array<int> p{new int[2]};
  *p.get() = 1;
  p[1] = 2;
  p.reset(new int[3]);
}

智能指针boost::scoped_array使用方法和boost::scoped_ptr类似,最主要的区别在于boost::scoped_array在析构函数中使用delete[]来释放它所包含的对象。由于该操作只适用于数组,所以boost::scoped_array必须使用一个指向动态分配的数组的指针来初始化。

boost::scoped_array在头文件boost/scoped_array.hpp中定义。

boost::scoped_array重载了[]操作符,可以使用[]操作符访问数组中的指定元素。所以boost::scoped_array类型的对象的行为与其保存的数组指针类似。例子1.2中在p中所保存的数组中的第二个元素中存入数字2。

和boost::scoped_ptr一样,成员函数get()可以被用于获取裸指针,reset()可以被用于重置包含的对象。

共享所有权

智能指针boost::shared_ptr和boost::scoped_ptr类似,它们的主要区别在于boost::shared_ptr不一定是某个对象的唯一所有者。所有权可以被其它boost::shared_ptr类型的智能指针所共享。在这种情况下,被共享的动态对象只有在最后一个引用它的共享指针结束生命周期后才会被释放。由于boost::shared_ptr可以共享所有权,所以该智能指针可以被拷贝,这对于boost::scoped_ptr是不可能的。

boost::shared_ptr在头文件boost/shared_ptr.hpp中定义。

Example1.3. 使用boost::shared_ptr

#include <boost/shared_ptr.hpp>
#include <iostream>

int main()
{
  boost::shared_ptr<int> p1{new int{1}};
  std::cout << *p1 << '\n';
  boost::shared_ptr<int> p2{p1};
  p1.reset(new int{2});
  std::cout << *p1.get() << '\n';
  p1.reset();
  std::cout << std::boolalpha << static_cast<bool>(p2) << '\n';
}

例1.3使用了两个boost::shared_ptr类型的智能指针p1和p2,p2使用p1来进行初始化,这意味着两个智能指针共享int类型对象的所有权。当p1调用reset()方法时,一个新的int类型对象被绑定入p1中,但这并不会使得原有的int类型对象被销毁,因为该对象也被p2所持有,所以该对象仍然存在。在reset()被调用后,p1是值为2的int类型对象的唯一所有者,p2是值为1的int类型对象的唯一所有者。

boost::shared_ptr内部使用引用计数。只有在boost::shared_ptr检测到最后一个引用某个动态对象的只能指针被销毁后,该对象才会被delete释放。

与boost::scoped_ptr类似,boost::shared_ptr重载了bool()运算符、*运算符和->运算符。也提供成员函数get()来获取裸指针,reset()函数来绑定一个新的对象.

boost::shared_ptr在构造时可以传入一个删除器作为第二个参数。这个删除器必须是一个函数或者一个函数对象,并且接受该实例化的boost::shared_ptr为唯一参数。在boost::shared_ptr析构时,该删除器取代delete被调用。这个特性使得boost::shared_ptr可以用于管理动态分配对象以外的其它资源。

Example 1.4. boost::shared_ptr和自定义删除器

#include <boost/shared_ptr.hpp>
#include <Windows.h>

int main()
{
  boost::shared_ptr<void> handle(OpenProcess(PROCESS_SET_INFORMATION, FALSE,
    GetCurrentProcessId()), CloseHandle);
}

在例1.4中,使用void实例化boost::shared_ptr。OpenProcess()的返回结果作为第一个参数被传递给构造函数。OpenProcess()是一个Windows中获取进程句柄的函数。在例子中,OpenProcess()返回指向当前进程的句柄,也就是该例子本身。

Windows中使用句柄来引用资源。当某个资源不再需要使用时,句柄必须使用CloseHandle()来关闭。CloseHandle()的唯一参数就是要被关闭的句柄。在例子中,CloseHandle()作为第二个参数被传入boost::shared_ptr的构造函数,也就是CloseHandle()是handle的删除器。当handle在main()函数结束被销毁时,智能指针的析构函数调用CloseHandle()来关闭作为第一个参数被传入智能指针构造函数的句柄。

注意:例1.4可以正常工作是因为Windows中将句柄定义为void类型。如果OpenProcess()不返回一个void类型的值或者CloseHandle()不接受一个void*类型的参数,那么在该例中就不能使用boost::shared_ptr。删除器并不能使boost::shared_ptr成为管理任意资源的万能工具。

Example 1.5. 使用boost::make_shared

#include <boost/make_shared.hpp>
#include <typeinfo>
#include <iostream>

int main()
{
  auto p1 = boost::make_shared<int>(1);
  std::cout << typeid(p1).name() << '\n';
  auto p2 = boost::make_shared<int[]>(10);
  std::cout << typeid(p2).name() << '\n';
}

Boost.SmartPointers在boost/make_shared.hpp中提供了一个工具函数boost::make_shared()。使用boost::make_shared()可以不直接调用boost::shared_ptr的构造函数来创建一个boost::shared_ptr类型的的只能指针。

使用boost::make_shared()的好处在于保存对象所用的动态内存和保存智能指针内部的引用计数器的内存可以被保存在一起。使用boost::make_shared()比先使用new来创建一个动态对象而后又在boost::shared_ptr的构造函数中使用new来创建引用计数器效率更高。

也可以对数组使用boost::make_shared()。在例1.5中,第二次调用boost::make_shared将一个有十个元素的数组绑定入p2中。

boost::shared_ptr从1.53.0版本开始开始支持数组。就像boost::scoped_ptr和boost::scoped_array的关系一样,boost::shared_array是一个类似于boost::shared_ptr的智能指针。如果使用Visual C++2013和Boost1.53.0或者更新版本来进行构建的话,例1.5中对于p2的类型会打印出class boost::shared_ptr<int [0]>。

从Boost1.53.0版本开始,boost::shared_ptr同时支持单个对象和数组,并且会自动检测需要用delete还是用delete[]来释放资源。因为boost::shared_ptr也重载了[]操作符(1.53.0版本后),所以它也可以作为boost::shared_array的替代品来使用。

特殊的智能指针

到目前为止介绍的每种智能指针都可以被独立地用在不同的场景中。但是boost::weak::ptr只有在和boost::shared_ptr一起使用是才有意义。boost::weak_ptr被定义在boost/weak_ptr.hpp中。

Example 1.8. 使用boost::weak_ptr

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <thread>
#include <functional>
#include <iostream>

void reset(boost::shared_ptr<int> &sh)
{
  sh.reset();
}

void print(boost::weak_ptr<int> &w)
{
  boost::shared_ptr<int> sh = w.lock();
  if (sh)
    std::cout << *sh << '\n';
}

int main()
{
  boost::shared_ptr<int> sh{new int{99}};
  boost::weak_ptr<int> w{sh};
  std::thread t1{reset, std::ref(sh)};
  std::thread t2{print, std::ref(w)};
  t1.join();
  t2.join();
}

boost::weak::ptr必须用一个boost::shared_ptr来进行初始化。它最重要的成员函数是lock()。lock()返回一个与该weak指针初始化时的传入的shared指针共享所有权的一个boost::shared_ptr。如果shared指针是空的,那么返回的指针也会是空的。

boost::weak_ptr可以使用的场景为:当某个函数中需要用到某个由shared指针管理的对象,但是该对象的生命周期却不应该取决于该函数本身。这个函数只有在程序中别的地方至少还有一个shared指针享有该对象的所有权时才能使用该对象。这样可以使得如果shared指针被reset了,被管理的对象不会由于在函数中还有一个额外的shared指针的存在而继续存活。

例1.8中在main函数创建了两个线程,第一个线程接收一个shared指针的引用作为参数,并在线程中调用了reset()。第二个线程中接收一个weak指针的引用作为参数,并在线程调用了print()。这个weak指针在此前用上述的shared指针初始化。

程序开始运行时,reset()和print()时同时执行的,但是它们的执行顺序是不确定的。这就带来了在print()被调用时,对象刚好被reset()销毁的潜在问题。

而weak指针可以解决这一问题:调用lock()时,如果对象仍然存活,则返回一个可用的shared指针。如果对象已经被销毁,则返回的shared指针被置为0,与一个空指针等价。

boost::weak::ptr本身不会对对象的生命周产生影响。为了安全地在print()函数中访问该对象,lock()返回一个boost::shared_ptr。这保证了即使另一个线程中试图释放该对象,这个对象也会因为这个返回的shared指针而继续存活。

posted on 2019-11-29 15:04  caiminfeng  阅读(278)  评论(0编辑  收藏  举报

导航