C++智能指针使用陷阱、shared_ptr实现

一 智能指针使用概述

1.使用场景

1.1 unique_ptr

1.1.1 概念

std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。

std::unique_ptr 常用于管理对象的生存期,包含:

  • 通过正常退出和经由异常退出两者上的受保证删除,提供异常安全,给处理拥有动态生存期的对象的类和函数
  • 传递独占的拥有动态生存期的对象的所有权到函数
  • 从函数获得独占的拥有动态生存期对象的所有权
  • 作为具移动容器的元素类型,例如保有指向动态分配对象的指针的 std::vector (例如,若想要多态行为)
1.1.2 使用追述
 
struct B {
  virtual void bar() { std::cout << "B::bar\n"; }
  virtual ~B() = default;
};
struct D : B
{
    D() { std::cout << "D::D\n";  }
    ~D() { std::cout << "D::~D\n";  }
    void bar() override { std::cout << "D::bar\n";  }
};
 
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr pass_through(std::unique_ptr p)
{
    p->bar();
    return p;
}
 
void close_file(std::FILE* fp) { std::fclose(fp); }
 
int main()
{
  std::cout << "unique ownership semantics demo\n";
  {
      auto p = std::make_unique(); // p 是占有 D 的 unique_ptr
      auto q = pass_through(std::move(p)); 
      assert(!p); // 现在 p 不占有任何内容并保有空指针
      q->bar();   // 而 q 占有 D 对象
  } // ~D 调用于此
 

 唯一性指针删除了拷贝构造和赋值重载,只保留了移动构造和移动赋值:

auto q = pass_through(std::move(p)); 

此代码也可运行通过:

std::unique_ptrres(std::move(p));

 唯一性指针可以设置自定义删除器

int main()
{
std::cout << "Custom lambda-expression deleter demo\n";
	{
		std::unique_ptr> p(new D, [](D* ptr)
			{
				std::cout << "destroying from a custom deleter...\n";
				delete ptr;
			});  // p 占有 D
		p->bar();
	} // 调用上述 lambda 并销毁 D
}

 执行结果:

Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D

E:\ccc\helloc\Debug\helloc.exe (进程 9996)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

1.2 shared_ptr

1.2.1概念

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。

shared_ptr 能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。存储的指针为 get() 、解引用及比较运算符所访问。被管理指针是在 use_count 抵达零时传递给删除器者。

1.2.2循环引用问题

代码:

struct D;
using namespace std;
struct B {
	std::shared_ptrmb;//观察res1
	 void bar() { std::cout << "B::bar\n"; }
	 B() { std::cout << "B::bar\n"; }
	 B(std::shared_ptrp) :mb(p){ std::cout << "B::create\n"; }
	 ~B() { std::cout << "B::~D\n"; }
};
struct D 
{
	D() { std::cout << "D::CREATE\n"; }
	std::shared_ptrmd;//观察res2
	D(std::shared_ptrp) :md(p){ std::cout << "D::D\n"; }
	~D() { std::cout << "D::~D\n"; }
	void bar()  { std::cout << "D::bar\n"; }
};

int main()
{
	shared_ptrres1(new D);
	shared_ptrres2 = std::make_shared(res1);
	res1->md = res2;

}

问题造成的结果:

无法释放被管理的对象。

问题的原因:

1、 此两个智能指针释放时,uses减少1为1,并不会释放ptr资源.

C++智能指针使用陷阱、shared_ptr实现_第1张图片

问题的解决办法:

使用弱引用智能指针。

1.uses和weaks初始都为1。

C++智能指针使用陷阱、shared_ptr实现_第2张图片

2.在相互赋值智能指针成员对象时发生:

C++智能指针使用陷阱、shared_ptr实现_第3张图片

 根据函数入栈顺序,先析构res1,uses由1减少1为0,weaks由2减少为1;析构指向的资源,并将ptr中rep指向的weaks引用计数减少1为1;

之后析构res2,uses由1减少为0,weaks由1减少为0,所以释放引用计数器;析构ptr指向的资源,将weaks由1降为0;释放引用计数器。

1,2.3线程安全

1.2.3.1 概述

多个线程能在 shared_ptr 的不同实例上调用所有成员函数(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是副本,且共享同一对象的所有权。若多个执行线程访问同一 shared_ptr 而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;原子函数的 shared_ptr 特化能用于避免数据竞争。或者是进行同步操作。

1.2.3.2 多线程智能指针的写入会导致问题

多线程下的智能指针读操作不会产生线程安全问题。

1.2.3.3多线程下的智能指针写操作会产生线程安全问题

shared_ptr& Message::GetInstance()
{
	std::lock_guardlock(m_mtx);
	m_instance = std::shared_ptr(new Message);
	m_instance->AddWorkthread();
	return m_instance;
}

void Message::AddWorkthread()
{
	static  weak_ptrwp = shared_from_this();
	 pwth =new thread (&Message::WorkThread,ref(wp)); 
}

void Message::WorkThread(std::weak_ptr&pm)
{

	while (true)
	{
		shared_ptr	pa = pm.lock();
		if (!pa)return;
		std::lock_guardlock(pa->ms_mtx);
		if (pa->m_stop)return;
		if (!pa->m_message.empty())
		{
			std::cout << pa->m_message << std::endl;
			pa->m_message.clear();
		}
	}
		
}

void Message::AddWorkthread()函数中的创建线程函数中传入的weak_ptr参数是引用传递时,要注意的是此智能指针的生存期。看如下例子

void Message::AddWorkthread()
{
	 weak_ptrwp = shared_from_this();
	 pwth =new thread (&Message::WorkThread,ref(wp)); 
}

 将全局的wp改成局部,那么当启动线程时,wp被析构,程序崩溃。

使用weak_ptr lock()获取资源的方法
void Message::AddWorkthread()
{
	 weak_ptrwp = shared_from_this();
	 pwth =new thread (&Message::WorkThread,wp);
	//m_tha.detach();
}

void Message::WorkThread(std::weak_ptrpm)
{

	while (true)
	{
		shared_ptr	pa = pm.lock();
		if (!pa)return;
		std::lock_guardlock(pa->ms_mtx);
		if (pa->m_stop)return;
		if (!pa->m_message.empty())
		{
			std::cout << pa->m_message << std::endl;
			pa->m_message.clear();
		}
	}
		
}

三 共享型智能指针使用陷阱

最常见的使用错误:

1.使用相同的内置指针初始化多个智能指针

这会导致多个智能指针并不会指向同一个引用计数器。

2.不要用裸指针作为智能指针的参数传入

void func(shared_ptr ptr) {
	return;
}
int* p = new int(100);//裸指针
func(shared_ptr(p));

ptr在函数栈被释放时,资源也被立即释放。而你之后再继续使用那不就爆炸了嘛。

3.不要轻易使用get()取得裸指针。

智能指针就是怕裸指针出事情才出现的。所以最好不要做此操作。

4.使用shared_from_this返回this是安全的

先看一个不安全的例子:
 

struct Bad
{
    std::shared_ptr getptr() {
        return std::shared_ptr(this);
    }
    ~Bad() { std::cout << "Bad::~Bad() called\n"; }
};

void testBad()
{
    // Bad, each shared_ptr thinks it's the only owner of the object
    std::shared_ptr bad0 = std::make_shared();
    std::shared_ptr bad1 = bad0->getptr();
    std::cout << "bad1.use_count() = " << bad1.use_count() << '\n';
} // UB: Bad 的二次删除

int main()
{
    testBad();
}

 bad1的构建其实和bad0并非同一个引用技术器。所以最后依次析构时资源被释放两次。

5.栈空间创建的对象却用Sared_from_this返回this

struct Good : std::enable_shared_from_this // 注:公开继承
{
    std::shared_ptr getptr() {
        return shared_from_this();
    }
};
void misuseGood()
{
     //坏:调用 shared_from_this 但没有 std::shared_ptr 占有调用者
    try {
        Good not_so_good;
        std::shared_ptr gp1 = not_so_good.getptr();
        std::cout << gp1 << std::endl;
    }
    catch (std::bad_weak_ptr& e) {
        // 未定义行为(C++17 前)/抛出 std::bad_weak_ptr (C++17 起)
         std::cout << e.what() << '\n';
    }
}
int main()
{
    misuseGood();

}

非new出的空间就不要使用智能指针了。

你可能感兴趣的:(C++,c++,开发语言)