智能指针C++详解 C++11

智能指针

为什么不用裸指针

  • 裸指针在声明中并没有指出,裸指针指涉到的是单个对象还是一个数组。
  • 裸指针在声明中也没有提示在使用完指涉的对象以后,是否需要析构它。换言之,你从声明中看不出来指针是否拥有(own)其指涉的对象。
  • 即使知道需要析构指针所指涉的对象,也不可能知道如何析构才是适当的。是应该使用delete运算符呢,还是别有它途(例如,可能需要把指针传入一个专门的、用千析构的函数)
  • 即使知道了应该使用delete运算符,参见理由1’还是会发生到底应该用单个对象形式(“delete”)还是数组形式(delete[])的疑问。一旦用错,就会导致未定义行为。
  • 没有什么正规的方式能检测出指针是否空悬(dangle),也就是说,它指涉的内存是否已经不再持有指针本应该指涉的对象。如果一个对象已经被析构了,而某些指针仍然指涉到它,就会产生空悬指针。

c++ 中的智能指针

C++ll 中共有四种智能指针:

  • std::auto_ptr (弃用)
  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr
    所有这些智能指针都是为管理动态分配对象的生命期而设计的,
    换言之,通过保证这样的对象在适当的时机以适当的方式析构(包括发生异常的场合),
    来防止资源泄漏。

专属所有权 unique_ptr

std::unique_ptr实现的是专属所有权语义。一个非空的std::unique_ptr总是拥有其所指涉到的资源。移动一个std::unique_ptr会将所有权从源指针移至目标指针(源指针被置空)。
std::unique_ptr不允许复制,因为如果复制了一个std::unique_ptr,就会得到两个指涉到同一资源的std::unique_ptr,而这两者都认为且已拥有(因此应当析构)该资源。因而,std::unique_ptr是个只移型别。在执行析构操作时,由非空的std::unique_ptr析构其资源。默认地,资源的析构是通过对std::unique_ptr内部的裸指针实施delete完成的。

  1. unique_ptr 是可以自定义析构函数的
      /// Destructor, invokes the deleter if the stored pointer is not null.
      ~unique_ptr() noexcept
      {
	static_assert(__is_invocable<deleter_type&, pointer>::value,
		      "unique_ptr's deleter must be invocable with a pointer");
	auto& __ptr = _M_t._M_ptr();
	if (__ptr != nullptr)
	  get_deleter()(std::move(__ptr)); 
      /* 这里比较奇怪(看起来)  
      实际上是get_deleter()返回构造时传入的可调用对象 
      然后对可调用对象进行调用 
      这里的__ptr的类型是auto将由编译时编译器自行推断  */
	__ptr = pointer();
      }

在这个析构函数中,首先使用static_assert进行断言检查,确保deleter_type类型的对象可以通过指针调用。如果无法调用,则会触发编译错误。
接下来,通过引用_M_t._M_ptr()获取存储的指针,并将其赋值给__ptr变量。然后检查__ptr是否为nullptr,即指针是否为空。
如果__ptr不为空,则调用get_deleter()函数获取deleter对象,并将__ptr作为参数传递给它。这里使用std::move(__ptr)将__ptr转移所有权,以确保在调用完毕后__ptr被置空。
最后,将__ptr赋值为pointer(),即将其置为空指针。

  • 如果由 makelnvestment 创建的对象不应被直接删除,而是应该先写入一条日志,那么 makelnvestment 可以像下面这样实现

image.png
这里的传入的是一个可调用对象 lambda OR class() OR func()

在使用默认析构器(即 delete 运算符)的前提下,std::unique_ptr 和裸指针尺寸相同。自定义析构器现身以后,情况便有所不同了。若析构器是函数指针,那么 std::unique_ptr 的尺寸 一 般会增加 一 到两个字长 (word) 。而若析构器是函数对象,则带来的尺寸变化取决千该函数对象中存储了多少状态。无状态的函数对象(例如,无捕获的 lambda 表达式)不会浪费任何存储尺寸。

共享所有权 shared_ptr

通过std::shared_ptr这种智能指针访问的对象采用共享所有权来管理其生存期。没有哪个特定的std::shared_ptr拥有该对象。取而代之的是,所有指涉到它的std::shared_ptr共同协作,确保在不再需要该对象的时刻将其析构。当最后一个指涉到某对象的std::shared_ptr不再指涉到它时(例如,由于是该std::shared_ptr被析构,或使其指涉到另一个不同的对象),该std::shared_ptr会析构其指涉到的对象。

std::shared_ptr 的构造函数会使该计数递增(通常如此),而其析构函数会使该计数递减,而复制赋值运算符同时执行两种操作;

引用计数的存在会带来 一 些性能影响:

  1. 内存 内存是裸指针的两倍
  2. 引用技术需要动态分配
  3. 计数的递增和递减必须是原子操作 (慢于非原子操作 即使计数只有一个字节)
析构

unique_ptr 相似 shared_ptr 同样支持自定义析构器
但支持的方式和unique_ptr并不相同
对于 unique_ptr 而言,析构器的型别是智能指针型别的一部分。

image.png


内存

image.png

关于 shared_ptr 上述内存何时创建

  1. std::make_shared 总是创建一个控制块.
  2. 从具备专属所有权的指针(即std::unique_ptr或std::auto_ptr指针)出发构造一个std::shared_ptr时,会创建—个控制块 。

    构造后 专属所有权的指针 的所有权转移 其被置空

  3. 当 std::shared_ptr 构造函数使用裸指针作为实参来调用时

    使用裸指针创建时要注意 以免两个sharePtr指向同一内存
    进行错误析构 进而野指针

注意
  1. 在类内不要使用this去构造shared_ptr
    image.png
    使用
    class M : public std::enable_shared_from_this<M> 进行继承操作
    并使用 成员函数 shared_from_this() 获取此对象的 shared_ptr
    image.png
    如上所示
    上述 仅仅作为演示

image.png
2. 关于 weak_ptr

  • 关于 weak_ptr 的使用场景
  • 校验指针是否悬空
  • 弱回调
  • 缓存及其观察者
  • 避免shared_ptr循环引用

  1. 非必要 则使用std::make_ptr
    优先选用 std::make_unique 和 std::make_shared, 而非直接使用 new

     void processWidget(std::shared_ptr<Widget> sqw,int priority){
       // .....
     }
     int computerPriority(){
       // ....
       return 1;
     }
    
     int func(){
       processWidget(std::make_shared< Widget>( new Widget),
       computerPriority()); // 潜在的内存(资源)泄漏
     }
    
    

    原因与编译器从源代码到目标代码的翻译有关。在运行期,传递给函数的实参必须在函数调用被发起之前完成评估求值。下列事件必须在 processWidget 开始执行前发生:

    • 表达式 “new Widget" 必须先完成评估求值,即, 一个 Widget 对象必须先在堆上创建。
    • 由 new 产生的裸指针的托管对象 std::shared_ptr 的构造函数必须执行。
    • computePriority 必须运行。

    编译器不必按上述顺序来生成代码。
    其有可能生成

    1. 实施 “ new Widget" 。
    2. 执行 computePriority 。
    3. 运行 std::shared_ptr 构造函数。

    如果生成了这样的代码,并且在运行期 computePriority 产生了异常,那么由第一步动态分配的Widget会被泄漏,因为它将永远不会被存储到在第三步才接管的std::shared_ptr中去。

    使用 std::make_shared 可以避免该问题。

    产生这种歧义的原因可能是 编译优化时进行评估求知? 咱也不知道QAQ

  • BUT
    • 相对于直接使用 new 表达式,优先选用 make 函数 但是所有的make函数 不能自定 析构器 需要自定析构器的智能指针 还是得 std::shared_ptr<T>(new T, ...)

    • 关于列表构造
      std::make_shared<std::vector<int>> (10,20)
      std::make_shared<std::vector<int>> {10,20}
      其构造的 是 [10,20] 还是 [20,20,20,20,20…]
      答案是

      1. std::make_shared<std::vector<int>> {10,20} 编译不通过
      2. std::make_shared<std::vector<int>> (10,20) 构造的是[20,20,20,20,20…]

      为了使用列表构造+make函数
      我们可以

      auto i = {10,20};
      std::make_shared<std::vector<int>> (i);
      
  1. std::shared_ptr 的 API 仅被设计用来处理指涉到单个对象的指针 但unique_ptr 可以是数组
  2. 关于 shared_ptr 是 make 构造 还是 new 构造时 关于 weak_ptr 的一些差别
    #include <iostream>
    #include <memory>
    
    class Resource {
    public:
        Resource() {
            std::cout << "Resource acquired." << std::endl;
        }
        ~Resource() {
            std::cout << "Resource released." << std::endl;
        }
        int value = 10;
    };
    int main() {
        std::weak_ptr<Resource> weakPtr;
        Resource * i = nullptr;
        {
            auto sharedPtr = std::make_shared<Resource>();
            weakPtr = sharedPtr;
            i = sharedPtr.get();
            if (auto lockedPtr = weakPtr.lock()) {
                std::cout << "Resource is still available." << std::endl;
            } else {
                std::cout << "Resource has been released." << std::endl;
            }
        }
    
        std::cout << i->value << std::endl;
    
        if (auto lockedPtr = weakPtr.lock()) {
            std::cout << "Resource is still available." << std::endl;
        } else {
            std::cout << "Resource has been released." << std::endl;
        }
    
        std::cout << i->value << std::endl;
    
        return 0;
    }
    
    #include <iostream>
    #include <memory>
    
    class Resource {
    public:
        Resource() {
            std::cout << "Resource acquired." << std::endl;
        }
        ~Resource() {
            std::cout << "Resource released." << std::endl;
        }
        int value = 10;
    };
    int main() {
        std::weak_ptr<Resource> weakPtr;
    
        Resource * i = nullptr;
        {
            std::shared_ptr<Resource> sharedPtr (new Resource);
            weakPtr = sharedPtr;
            i = sharedPtr.get();
            if (auto lockedPtr = weakPtr.lock()) {
                std::cout << "Resource is still available." << std::endl;
            } else {
                std::cout << "Resource has been released." << std::endl;
            }
        }
    
        std::cout << i->value << std::endl;
    
        if (auto lockedPtr = weakPtr.lock()) {
            std::cout << "Resource is still available." << std::endl;
        } else {
            std::cout << "Resource has been released." << std::endl;
        }
    
        std::cout << i->value << std::endl;
    
        return 0;
    }
    
    运行上面的例子 可知:
    通过 std::make_shared(…)创建shared_ptr真正free内存的时机是 ---- 引用计数和弱引用计数全部为0时
    通过 std::shared_ptr(new T(…))创建shared_ptr真正free内存的时机是 ---- 引用计数为0时
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值