书接上文,如果我们不做拷贝,只是将指针的所有权传递出去。我们应该如何实现?
C++的 等号运算符重载 有点类似复制构造函数,将等号(=)右边的本类对象的值复制给等号左边的对象。
通过重载等号运算符,将“Auto_ptr”切换为普通指针,并且将Auto_ptr置空。
#include <iostream>
template<class T>
class Auto_ptr
{
T* m_ptr;
public:
Auto_ptr(T* ptr=nullptr)
:m_ptr(ptr)
{
}
~Auto_ptr()
{
delete m_ptr;
}
// A copy constructor that implements move semantics
Auto_ptr(Auto_ptr& a) // note: not const
{
m_ptr = a.m_ptr; // transfer our dumb pointer from the source to our local object
a.m_ptr = nullptr; // make sure the source no longer owns the pointer
}
// An assignment operator that implements move semantics
Auto_ptr& operator=(Auto_ptr& a) // note: not const
{
if (&a == this)
return *this;
delete m_ptr; // make sure we deallocate any pointer the destination is already holding first
m_ptr = a.m_ptr; // then transfer our dumb pointer from the source to the local object
a.m_ptr = nullptr; // make sure the source no longer owns the pointer
return *this;
}
T& operator*() const { return *m_ptr; }
T* operator->() const { return m_ptr; }
bool isNull() const { return m_ptr == nullptr; }
};
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
Auto_ptr<Resource> res1(new Resource());
Auto_ptr<Resource> res2; // Start as nullptr
std::cout << "res1 is " << (res1.isNull() ? "null\n" : "not null\n");
std::cout << "res2 is " << (res2.isNull() ? "null\n" : "not null\n");
res2 = res1; // res2 assumes ownership, res1 is set to null
std::cout << "Ownership transferred\n";
std::cout << "res1 is " << (res1.isNull() ? "null\n" : "not null\n");
std::cout << "res2 is " << (res2.isNull() ? "null\n" : "not null\n");
return 0;
}
移动而非拷贝,这就是move semantics。
但这就万无一失了吗?
std::auto_ptr是通过拷贝构造函数和赋值运算符重载实现移动语义,把一个std::auto_ptr传值给一个函数,会造成auto_ptr指向的资源被转移给了函数的参数。函数参数是一个局部变量,在函数执行完成之后就会被销毁,其指向的资源也会被销毁。然后调用者如果继续使用auto_ptr就会得到一个空指针,造成程序crash(只能用一次,除非再次申请)。
其次,std::auto_ptr释放内存总是用delete xxx,而不是delete[] xxx, 这就意味着auto_ptr不能正确释放动态分配的数组。更糟糕的是,如果你把指向数组的指针传给auto_ptr,它不会报任何错误或警告,这样看下来,就会导致内存泄漏问题。
最后,auto_ptr不能处理C++标准库中的其他类,包括大多数容器和算法类。这是因为这些类在做copy的时候确实是做了copy而不是move。
基于上述原因,auto_ptr在C++11不推荐使用。
正是因为这个原因,c++11正式定义了移动语义,并且提供了三种智能指针。
std::unique_ptr
-
独占语义
一个非空的unique_ptr永远独占其指向的对象 -
move-only
会把所有权从源指针转向目的指针,这时候源指针会指向null
析构的时候,会delete来释放原生指针指向的空间 -
拷贝禁止
std::unique_ptr 不允许拷贝 -
std::unique_ptr 开销小,速度快。
move-only特定的智能指针使用独占语义管理资源。并且可以轻易转化为shared_ptr。
std::weak_ptr
- weak_ptr是一种不控制所指向对象生存期的智能指针(弱引用),它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
std::shared_ptr使用了引用计数,记录指向资源的std::shared_ptr个数
对性能的影响:
-
大小是原生指针的两倍大小,内部除了包含一个指向资源的原生指针,还有资源的引用计数
-
引用计数的递增或者递减必须是原子的,这部分会引起一定开销
-
move-construct 比 copy-constructor一个shared_ptr要快,copy需要修改引用计数,move则不需要。
评论区