侧边栏壁纸
博主头像
赵东阳的个人网站

行动起来,活在当下

  • 累计撰写 20 篇文章
  • 累计创建 8 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录
C++

智能指针、移动语义(二)

温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

书接上文,如果我们不做拷贝,只是将指针的所有权传递出去。我们应该如何实现?

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

  1. 独占语义
    一个非空的unique_ptr永远独占其指向的对象

  2. move-only
    会把所有权从源指针转向目的指针,这时候源指针会指向null
    析构的时候,会delete来释放原生指针指向的空间

  3. 拷贝禁止
    std::unique_ptr 不允许拷贝

  4. std::unique_ptr 开销小,速度快。
    move-only特定的智能指针使用独占语义管理资源。并且可以轻易转化为shared_ptr。

std::weak_ptr

  1. weak_ptr是一种不控制所指向对象生存期的智能指针(弱引用),它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。

std::shared_ptr使用了引用计数,记录指向资源的std::shared_ptr个数

对性能的影响:

  1. 大小是原生指针的两倍大小,内部除了包含一个指向资源的原生指针,还有资源的引用计数

  2. 引用计数的递增或者递减必须是原子的,这部分会引起一定开销

  3. move-construct 比 copy-constructor一个shared_ptr要快,copy需要修改引用计数,move则不需要。

0

评论区