C++智能指针使用指南

智能指针是代理模式的具体应用,它使用 RAII 技术代理了裸指针,能够自动释放内存, 无需程序员干预,所以被称为“智能指针”。

智能指针不是指针,而是一个对象,所以不要对其调用delete,它会自动管理初始化时的指针,在离开作用域时析构释放内存。

智能指针也没有定义加减运算,不能随意移动指针地址,这样避免了指针越界操作。

在使用上:

如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全 。

如果指针是“共享”使用,就应该选择 shared_ptr,它的功能非常完善,用法几乎与原始指针一样

使用智能指针要加头文件#include <memory>

工厂函数make_unique()、make_shared()不只是返回智能指针对象,其内部也有优化。

如果你已经理解了智能指针,就尽量不要再使用裸指针、new 和 delete 来操作内存了。

unique_ptr

unique_ptr需要手动初始化,声明的时候必须用模板参数指定类型:

unique_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 = 10); // 可以使用*取内容
assert(ptr1 != nullptr); // 可以判断是否为空指针
unique_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello"); // 可以使用*取内容
assert(ptr2->size() == 5); // 可以使用->调用成员函数

也可以调用工厂函数,强制创建智能指针的时候必须初始化:

auto ptr3 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42);
auto ptr4 = make_unique<string>("god of war"); // 工厂函数创建智能指针
assert(!ptr4->empty());

unique_ptr表示该智能指针的所有权是唯一的,不允许共享,任何时候只能有一个人持有。

它禁止拷贝赋值,但是可以使用std::move()显式地声明所有权转移:

auto ptr1 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr1 && *ptr1 == 42); // 此时智能指针有效
auto ptr2 = ptr1; // 编译有问题
auto ptr2 = std::move(ptr1); // 使用move()转移所有权
assert(ptr2 && *ptr2 == 42);
assert(ptr1); // 此时智能指针无效.会报错

指针的所有权就被转走了,原来的 unique_ptr 变成了空指针,新的 unique_ptr 接替了管理权,保证所有权的唯一性

shared_ptr

基本使用方式与unique_ptr并无不同:

shared_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 = 10); // 可以使用*取内容
shared_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello"); // 可以使用*取内容
auto ptr3 = make_shared<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42); // 可以判断是否为空指针
auto ptr4 = make_shared<string>("zelda"); // 工厂函数创建智能指针
assert(!ptr4->empty()); // 可以使用->调用成员函数

不过它的所有权可以被安全共享,支持拷贝赋值

auto ptr1 = make_shared<int>(42); // 工厂函数创建智能指针
assert(ptr1 && ptr1.unique() ); // 此时智能指针有效且唯一
auto ptr2 = ptr1; // 直接拷贝赋值,不需要使用move()
assert(ptr1 && ptr2); // 此时两个智能指针均有效
assert(ptr1 == ptr2); // shared_ptr可以直接比较
// 两个智能指针均不唯一,且引用计数为2
assert(!ptr1.unique() && ptr1.use_count() == 2);
assert(!ptr2.unique() && ptr2.use_count() == 2);

其内部使用引用计数,所以具有完整的”值语义“,可以在任何场合下代替原始指针。

不过维护引用计数的存储和管理都是成本,过度使用会降低运行效率。其引用计数也会带来循环引用,下面是简化后的典型例子:

#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
    
    
public:
    using this_type = Node;
    using shared_type = std::shared_ptr<this_type>;
    shared_type next;   // 使用只能指针来指向下一个节点
};
int main() {
    
    
    auto n1 = make_shared<Node>();  // 工厂函数创建智能指针
    auto n2 = make_shared<Node>();

    // 此时引用计数均为1
    assert(n1.use_count() == 1);
    assert(n2.use_count() == 1);

    // 产生循环引用
    n1->next = n2;
    n2->next = n1;

    // 此时引用计数均为2,且无法减到0,内存泄露
    assert(n1.use_count() == 2);
    assert(n2.use_count() == 2);
}

这个例子很简单,你一下子就能看出存在循环引用。但在实际开发中,指针的关系可不像例 子那么清晰,很有可能会不知不觉形成一个链条很长的循环引用,复杂到你根本无法识别, 想要找出来基本上是不可能的。 想要从根本上杜绝循环引用,光靠 shared_ptr 是不行了,必须要用到weak_ptr

weak_ptr

它专门为打破循环引用而设计,只观察指针,不会增 加引用计数(弱引用),但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用) 。用法如下

#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
    
    
public:
    using this_type = Node;
    // 改用weak_ptr
    using shared_type = std::weak_ptr<this_type>;
    shared_type next;   // 使用只能指针来指向下一个节点
};
int main() {
    
    
    auto n1 = make_shared<Node>();  // 工厂函数创建智能指针
    auto n2 = make_shared<Node>();

    // 此时引用计数均为1
    assert(n1.use_count() == 1);
    assert(n2.use_count() == 1);

    // 产生循环引用
    n1->next = n2;
    n2->next = n1;

    // 因为使用了weak_ptr,引用计数为1
    assert(n1.use_count() == 1);
    assert(n2.use_count() == 1);
    if (!n1->next.expired()) {
    
      // 检查指针是否有效
        auto ptr = n1->next.lock(); // lock()获取shared_ptr
        assert(ptr == n2);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_42604176/article/details/120619028