写给[C++ ]新人智能指针避坑指南

最近在整理一些 C++ 智能指针的使用和避坑方面的资料,感兴趣的不妨看看

std::unique_ptr

unique_ptr 它是一种独占资源所有权的指针,unique_ptr 会在栈上分配,然后在离开作用域之后进行释放,删除里面持有的 Resource 对象。

在 C++ 11 的时候,我们可以这么使用 unique_ptr :

#include <iostream>
#include <memory> // for std::unique_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
    // allocate a Resource object and have it owned by std::unique_ptr
    std::unique_ptr<Resource> res{ new Resource() };

    return 0;
} // res goes out of scope here, and the allocated Resource is destroyed

在 C++14 的时候新加入了 make_unique 函数,我们可以利用它构造一个 unique_ptr 对象(支持数组对象):

#include <memory> // for std::unique_ptr and std::make_unique
#include <iostream>

class Fraction
{
private:
    int m_numerator{ 0 };
    int m_denominator{ 1 };

public:
    Fraction(int numerator = 0, int denominator = 1) :
        m_numerator{ numerator }, m_denominator{ denominator }
    {
    }

    friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
    {
        out << f1.m_numerator << '/' << f1.m_denominator;
        return out;
    }
};

int main()
{
    // Create a single dynamically allocated Fraction with numerator 3 and denominator 5
    // We can also use automatic type deduction to good effect here
    auto f1{ std::make_unique<Fraction>(3, 5) };
    std::cout << *f1 << '\n';

    // Create a dynamically allocated array of Fractions of length 4
    auto f2{ std::make_unique<Fraction[]>(4) };
    std::cout << f2[0] << '\n';

    return 0;
}

输出:

3/5
0/1

操作 unique_ptr

unique_ptr 删除了 删除了 copy constructor 和 copy assignment operator ,所以在在赋值的时候会使用 move 语义,在转移对象的时候必须使用 move 去转移:

#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
    std::unique_ptr<Resource> res1{ new Resource{} }; // Resource created here
    std::unique_ptr<Resource> res2{}; // Start as nullptr

    std::cout << "res1 is " << (res1 ? "not null\n" : "null\n");
    std::cout << "res2 is " << (res2 ? "not null\n" : "null\n");

    // res2 = res1; // Won't compile: copy assignment is disabled
    res2 = std::move(res1); // res2 assumes ownership, res1 is set to null

    std::cout << "Ownership transferred\n";

    std::cout << "res1 is " << (res1 ? "not null\n" : "null\n");
    std::cout << "res2 is " << (res2 ? "not null\n" : "null\n");

    return 0;
} // Resource destroyed here when res2 goes out of scope

输出:

Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed

unique_ptr 也重载了 operator*operator->operator*会返回对象的引用, operator->会返回对象的指针。需要注意的是,unique_ptr 所管理的对象可能发生转移,所以可能是空的,所以用这两个重载方法之前可以通过 if 判空:

#include <iostream>
#include <memory> // for std::unique_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    friend std::ostream& operator<<(std::ostream& out, const Resource &res)
    {
        out << "I am a resource";
        return out;
    }
};

int main()
{
    std::unique_ptr<Resource> res{ new Resource{} };

    if (res) // use implicit cast to bool to ensure res contains a Resource
        std::cout << *res << '\n'; // print the Resource that res is owning

    return 0;
}

输出:

Resource acquired
I am a resource
Resource destroyed

一般的情况,我们使用 unique_ptr 之后就不用管 unique_ptr 所管理的对象释放问题了,但是有时候我们依然想拿回对象的所有权,那么可以使用 release 函数返回 unique_ptr 所管理的对象的指针,并且释放对指针的控制权:

#include <iostream>
#include <memory>

int main() { 
    std::unique_ptr<int> uptr(new int(10));  //绑定动态对象 
    std::unique_ptr<int> uptr2 = std::move(uptr); //轉換所有權

    if(uptr == nullptr)
        cout<<"uptr give up *int"<<endl; 

    int * p = uptr2.release(); //uptr2释放对指针的控制权,返回指针,并将uptr2置为空

    if(uptr2 == nullptr)
        cout<<"uptr2 give up *int"<<endl; 

    cout<< *p <<endl; 
    delete p; 
    return 0;
}

unique_ptr 作为参数和返回值

unique_ptr 作为参数传递不会发生拷贝,但是会将对象所有权会转移到函数里,如下 ptr 会在 main 方法结束之前被销毁:

#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    friend std::ostream& operator<<(std::ostream& out, const Resource &res)
    {
        out << "I am a resource";
        return out;
    }
};

void takeOwnership(std::unique_ptr<Resource> res)
{
     if (res)
          std::cout << *res << '\n';
} // the Resource is destroyed here

int main()
{
    auto ptr{ std::make_unique<Resource>() };

//    takeOwnership(ptr); // This doesn't work, need to use move semantics
    takeOwnership(std::move(ptr)); // ok: use move semantics

    std::cout << "Ending program\n";

    return 0;
}

输出:

Resource acquired
I am a resource
Resource destroyed
Ending program

有时候不想对象的所有权转移到函数里,那么这时候可以通过 get 方法获取对象,如下:

#include <memory> // for std::unique_ptr
#include <iostream>

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }

    friend std::ostream& operator<<(std::ostream& out, const Resource &res)
    {
        out << "I am a resource";
        return out;
    }
};

// The function only uses the resource, so we'll accept a pointer to the resource, not a reference to the whole std::unique_ptr<Resource>
void useResource(Resource* res)
{
    if (res)
        std::cout << *res << '\n';
    else
        std::cout << "No resource\n";
}

int main()
{
    auto ptr{ std::make_unique<Resource>() };

    useResource(ptr.get()); // note: get() used here to get a pointer to the Resource

    std::cout << "Ending program\n";

    return 0;
} // The Resource is destroyed here

输出:

Resource acquired
I am a resource
Ending program
Resource destroyed

unique_ptr 可以直接作为返回值返回:

#include <memory> // for std::unique_ptr

std::unique_ptr<Resource> createResource()
{
     return std::make_unique<Resource>();
}

int main()
{
    auto ptr{ createResource() };

    // do whatever

    return 0;
}

在 C++14 和之前的版本中会使用 move 语义来返回 unique_ptr 对象,在 C++17和之后的版本中,由于强制开启了返回值优化,所以 move 操作也不需要了,进行了RVO优化(具体可以看这里:https://en.wikipedia.org/wiki/Copy_elision),所以直接返回值比返回裸指针或引用更安全

注意

不要让多个 unique_ptr 持有同一个对象,如下 res1 和 res2 将会试图释放多次 res:

Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
std::unique_ptr<Resource> res2{ res };

用 unique_ptr 持有对象之后不要手动再去 delete 对象,如下会导致一个对象被释放多次:

Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
delete res;

还需要注意异常问题,使用unique_ptr并不能绝对地保证异常安全,如下:

some_function(std::unique_ptr<T>(new T), function_that_can_throw_exception());

C++ 标准并没有规定编译器对函数参数的求值次序,所以有可能出现这样的次序:

  • 调用new T分配动态内存;
  • 调用function_that_can_throw_exception()函数;
  • 调用unique_ptr的构造函数;

假如调用 function_that_can_throw_exception 函数时抛出异常,而在这时 unique_ptr 还没构造,所以导致new T所分配的内存不能回收,造成了内存泄露。解决这个问题,需要使用make_unique函数:

some_function(std::make_unique<T>(), function_that_can_throw_exception());

因为对象 T 的创建和 unique_ptr 都是在 make_unique 函数里处理的,所以不会出现上面的顺序问题。

std::shared_ptr

shared_ptr 看这个名字就知道,和 unique_ptr 不同是它所管理的资源可以被多个对象持有。在底层实现中,shared_ptr 采用引用计数的方式实现,它里面有个内部控制器类型托管了一个计数器,这个内部控制器类型对象在shared_ptr第一次构造时以指针的形式保存在shared_ptr中。

shared_ptr重载了赋值运算符,在赋值和拷贝构造另一个shared_ptr时,这个指针被另一个shared_ptr共享。在引用计数归零时,这个内部类型指针与shared_ptr管理的资源一起被释放。

此外,为了保证线程安全性,引用计数器的加1,减1操作都是原子操作,它保证shared_ptr由多个线程共享时不会爆掉。

简单使用看下面这个例子:

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
    // allocate a Resource object and have it owned by std::shared_ptr
    Resource* res { new Resource };
    std::shared_ptr<Resource> ptr1{ res };
    {
        std::shared_ptr<Resource> ptr2 { ptr1 }; // make another std::shared_ptr pointing to the same thing

        std::cout << "Killing one shared pointer\n";
    } // ptr2 goes out of scope here, but nothing happens

    std::cout << "Killing another shared pointer\n";

    return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed

输出:

Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed

在上面的例子中, ptr2 在自己的作用域中被创建,然后出了作用域后 ptr2 虽然被销毁,但是所管理的资源却在 main 方法结束后才被销毁。

下面再看一个例子,这也是很多同学的错误用法之一:

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
    Resource* res { new Resource };
    std::shared_ptr<Resource> ptr1 { res };
    {
        std::shared_ptr<Resource> ptr2 { res }; // create ptr2 directly from res (instead of ptr1)

        std::cout << "Killing one shared pointer\n";
    } // ptr2 goes out of scope here, and the allocated Resource is destroyed

    std::cout << "Killing another shared pointer\n";

    return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed again

输出:

Resource acquired
Killing one shared pointer
Resource destroyed
Killing another shared pointer
Resource destroyed

上面这个例子会导致 crash,因为同一个资源被 delete 两次。我们注意到 ptr2 不是从 ptr1 copy 过来的,而是直接创建的,即使内部管理的资源是同一个,但是他们之间并没有关联,所以当 ptr2 走出作用域后会释放资源,ptr1 在 main 方法结束之后又会释放一次。

所以如果需要 shared_ptr 指向同一个资源,那么最佳实践是从已有的 shared_ptr copy 出来。

同样在 C++14 的时候新加入了 make_shared 函数,我们可以利用它构造一个 shared_ptr 对象:

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
    // allocate a Resource object and have it owned by std::shared_ptr
    auto ptr1 { std::make_shared<Resource>() };
    {
        auto ptr2 { ptr1 }; // create ptr2 using copy of ptr1

        std::cout << "Killing one shared pointer\n";
    } // ptr2 goes out of scope here, but nothing happens

    std::cout << "Killing another shared pointer\n";

    return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed

我们在 C++ 14 以后也要尽量使用 make_shared 来创建对象,它能保证在内存分配上能有更好的性能,也能函数参数的因编译器的初始化顺序而导致的问题(和上面 unique_ptr 问题一致)。

enable_shared_from_this

如果不小心直接在类里面返回 this 对象想要获得该对象的 shared_ptr,那么会让一个对象被 delete 两次,如下:

class Resource
{
public:
    Resource() { std::cerr << "Resource acquired\n"; }
    ~Resource() { std::cerr << "Resource destroyed\n"; }
    std::shared_ptr<Resource> GetSPtr() {
        return std::shared_ptr<Resource>(this);
    }
};

int main()
{
    auto sptr1 = std::make_shared<Resource>();
    auto sptr2 = sptr1->GetSPtr();
    return 0;
}

输出:

Resource acquired
Resource destroyed
double free or corruption (out)

上面的代码其实会生成两个独立的 shared_ptr,他们的控制块是独立的,所以导致 Resource 被释放了两次。

ptr

所以我们使用 enable_shared_from_this 可以避免上述情况:

class Resource : public std::enable_shared_from_this<Resource>
{
public:
    Resource() { std::cerr << "Resource acquired\n"; }
    ~Resource() { std::cerr << "Resource destroyed\n"; }
    std::shared_ptr<Resource> GetSPtr() {
        return shared_from_this();
    }
};

注意

第一个是资源释放问题,使用 shared_ptr 的时候一定要注意是否有正确的处置它,因为可能某个 shared_ptr 没有释放,从而导致它所管理的资源没有释放。对于 unique_ptr 使用你只需要关心一个对象是否被释放,但是 shared_ptr 你需要关心所有对象是否被释放。比如下面的循环引用问题:

#include <iostream>
#include <memory> // for std::shared_ptr
#include <string>

class Person
{
    std::string m_name;
    std::shared_ptr<Person> m_partner; // initially created empty

public:

    Person(const std::string &name): m_name(name)
    {
        std::cout << m_name << " created\n";
    }
    ~Person()
    {
        std::cout << m_name << " destroyed\n";
    }

    friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
    {
        if (!p1 || !p2)
            return false;

        p1->m_partner = p2;
        p2->m_partner = p1;

        std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';

        return true;
    }
};

int main()
{
    auto lucy { std::make_shared<Person>("Lucy") }; // create a Person named "Lucy"
    auto ricky { std::make_shared<Person>("Ricky") }; // create a Person named "Ricky"

    partnerUp(lucy, ricky); // Make "Lucy" point to "Ricky" and vice-versa

    return 0;
}

在上面我们创建了两个 Person Lucy 和 Ricky,然后让他们的内部 m_partner 相互赋值,结果就是他们两个对象形成了循环引用,都无法释放。

输出:

Lucy created
Ricky created
Lucy is now partnered with Ricky

第二是数组问题,在 C++17 以前 shared_ptr 是不能很好的托管数组对象的,所以不应该在 C++17 及以前使用 shared_ptr 托管 C 语言风格的数组。在 C++17 之前 shared_ptr 会通过 delete 而不是 delete[] 去删除被托管的数组。

所以我们有两种方式解决这种问题,第一种是使用 vector 代替 new[],如 shared_ptr<vector<int>>;第二种是自定义 deleter,如:

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

创建 shared_ptr 应该这样写:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

具体可以看这里:https://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used

第三是我们可以通过 unique_ptr 转成 shared_ptr,但是 shared_ptr 是不能转成 unique_ptr的。例如:

std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
std::shared_ptr<std::string> shared = std::move(unique);

//或者
std::shared_ptr<std::string> shared = std::make_unique<std::string>("test");

std::weak_ptr

在上面我们介绍了 shared_ptr 可能因为循环引用导致对象没有释放的问题,weak_ptr 就是设计用来解决这个问题。所以 weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它实际上不会托管对象,它指向一个由 shared_ptr 管理的对象而不影响所指对象的生命周期,也就是将一个 weak_ptr 绑定到一个shared_ptr不会改变 shared_ptr 的引用计数。

所以让我们看看上面的例子中如何用 weak_ptr 解决循环引用的问题:

#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>

class Person
{
    std::string m_name;
    std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr

public:

    Person(const std::string &name): m_name(name)
    {
        std::cout << m_name << " created\n";
    }
    ~Person()
    {
        std::cout << m_name << " destroyed\n";
    }

    friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
    {
        if (!p1 || !p2)
            return false;

        p1->m_partner = p2;
        p2->m_partner = p1;

        std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';

        return true;
    }
};

int main()
{
    auto lucy { std::make_shared<Person>("Lucy") };
    auto ricky { std::make_shared<Person>("Ricky") };

    partnerUp(lucy, ricky);

    return 0;
}

输出:

Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky destroyed
Lucy destroyed

因为 Person 内部用的是 weak_ptr ,所以在上面的例子中,我们将 lucy 的 m_partner 指向了 Ricky,但由于这是个 weak_ptr ,所以并不会计数,那么在 main 方法结束之后都能够正常销毁。

因为 weak_ptr 并没有实现 ->操作符,所以一般来说无法直接使用,所以我们可以通过 weak_ptr 的 lock 方法将 weak_ptr 转成 shared_ptr,如下 :

#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>

class Person
{
    std::string m_name;
    std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr

public:

    Person(const std::string &name) : m_name(name)
    {
        std::cout << m_name << " created\n";
    }
    ~Person()
    {
        std::cout << m_name << " destroyed\n";
    }

    friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
    {
        if (!p1 || !p2)
            return false;

        p1->m_partner = p2;
        p2->m_partner = p1;

        std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';

        return true;
    }

    const std::shared_ptr<Person> getPartner() const { return m_partner.lock(); } // use lock() to convert weak_ptr to shared_ptr
    const std::string& getName() const { return m_name; }
};

int main()
{
    auto lucy { std::make_shared<Person>("Lucy") };
    auto ricky { std::make_shared<Person>("Ricky") };

    partnerUp(lucy, ricky);

    auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
    std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';

    return 0;
}

输出:

Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky's partner is: Lucy
Ricky destroyed
Lucy destroyed

当然被转换的 share_ptr 引用计数也会增加,我们可以验证一下:

int main()
{
    auto lucy { std::make_shared<Person>("Lucy") };
    auto ricky { std::make_shared<Person>("Ricky") };

    partnerUp(lucy, ricky);
    std::cout <<  lucy.use_count() << endl; //输出:1

    auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
    std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';

    std::cout <<  lucy.use_count() << endl;  //输出:2
    return 0;
} 

weak_ptr 还可以避免悬挂指针

所谓悬挂指针(Dangling pointers)就是一个指针指向的对象被释放了,但是指针指向的目标并没有被修改,导致这个指针指向了一块被释放的内存,这样可能造成重复释放,还有其他不可预知的行为。

对于一个裸指针来说,我们是无法知道该指针指向的地址到底是否是一个悬挂指针,那么对于 weak_ptr 就可以通过 expired 方法来检测 weak_ptr 所指向的对象是否已经被释放,从而在一定程度上解决了这个问题。

我们先看一下例子:

// h/t to reader Waldo for an early version of this example
#include <iostream>
#include <memory>

class Resource
{
public:
    Resource() { std::cerr << "Resource acquired\n"; }
    ~Resource() { std::cerr << "Resource destroyed\n"; }
};

// Returns a std::weak_ptr to an invalid object
std::weak_ptr<Resource> getWeakPtr()
{
    auto ptr{ std::make_shared<Resource>() };
    return std::weak_ptr{ ptr };
} // ptr goes out of scope, Resource destroyed

// Returns a dumb pointer to an invalid object
Resource* getDumbPtr()
{
    auto ptr{ std::make_unique<Resource>() };
    return ptr.get();
} // ptr goes out of scope, Resource destroyed

int main()
{
    auto dumb{ getDumbPtr() };//返回的裸指针
    std::cout << "Our dumb ptr is: " << ((dumb == nullptr) ? "nullptr\n" : "non-null\n");

    auto weak{ getWeakPtr() };//返回weak_ptr
    std::cout << "Our weak ptr is: " << ((weak.expired()) ? "expired\n" : "valid\n");

    return 0;
}

输出:

Resource acquired
Resource destroyed
Our dumb ptr is: non-null
Resource acquired
Resource destroyed
Our weak ptr is: expired

在上面的例子中 getDumbPtr 方法里面使用的是 unique_ptr 来托管对象,并且返回的是里面托管对象的指针,那么该对象会在 getDumbPtr 方法结束的时候被销毁,所以 getDumbPtr 返回的是一个悬挂指针,最后我们发现 dumb 仍然指向了一片地址,但是该地址的对象已经被释放了。

对于 getWeakPtr 方法来说返回的是 weak_ptr,但是我们上面说了 weak_ptr 不会托管对象,所以在 getWeakPtr 方法结束的时候,getWeakPtr 所返回的对象实际上已经被 share_ptr 销毁了,所以在 main 函数中 weak 实际上指向了一个失效的内存区域,不过我们通过调用 weak.expired() 返回 true 可以知道它指向的对象已经被销毁了。

weak_ptr 实现缓存

因为是单独的 expired 操作,所以可能会有并发问题,比如在 expired 之后对象被销毁了,那么再去使用的话可能产生无法预料的结果。所以对于这种判断,我们可以交给 lock 函数,如果 weak_ptr 没有被销毁,那么会返回 shared_ptr ,相当于把它的生命周期延长了,因为它递增了一个引用计数,如果 weak_ptr 已经被销毁了,那么会返回 nil。

// Returns a std::weak_ptr to an invalid object
std::weak_ptr<Resource> getWeakPtr()
{
    auto ptr{ std::make_shared<Resource>() };
    return std::weak_ptr{ ptr };
} // ptr goes out of scope, Resource destroyed

int main()
{
    std::shared_ptr<Resource> spw = getWeakPtr().lock(); //return null
    std::cout << "Our share ptr is: " << ((spw == nullptr) ? "nullptr\n" : "non-null\n");
    std::shared_ptr<Resource> spw2(getWeakPtr()); //if object is expired,throw std::bad_weak_ptr
    return 0;
}

输出:

Resource acquired
Resource destroyed
Our share ptr is: nullptr
Resource acquired
Resource destroyed
terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr

在上面的例子中 spw 会返回 null,spw2 会抛出异常。所以利用 lock 我们可以实现缓存操作,比如我们有个全局的 map 作为 cache,里面的 value 是 weak_ptr 类型的,那么每次在获取 cache 的时候就可以判断 lock 返回的值是否为空,不为空直接返回,为空重新加载缓存:

std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
    static std::unordered_map<WidgetID,std::weak_ptr<const Widget>> cache;
    // objPtr is std::shared_ptr to cached object (or null
    // if object's not in cache)
    auto objPtr = cache[id].lock(); 

    if (!objPtr) { // if not in cache,
        objPtr = loadWidget(id); // load it
        cache[id] = objPtr; // cache it
    }
    return objPtr;
}

总结

unique_ptr 它是一种独占资源所有权的指针,unique_ptr 会在栈上分配,然后在离开作用域之后进行释放,删除里面持有的 Resource 对象,它只能使用 move 语义转移对象。所以如果你想操作一个指针,在进入作用域的时候分配好内存,然后在离开作用域的时候安全释放对象,那么可以使用它;

shared_ptr 它所管理的资源可以被多个对象持有,并且使用引用计数策略来释放对象,如果计数没有清零,那么它所管理的资源不会释放;

weak_ptr 它不管理对象,只是 shared_ptr 对象管理的资源的观察者,所以它不影响共享资源的生命周期,它一般使用在缓存、解决 shared_ptr 循环引用等地方。

Reference

https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one

https://stackoverflow.com/questions/4316727/returning-unique-ptr-from-functions

https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization

https://www.learncpp.com/cpp-tutorial/stdshared_ptr/

https://www.learncpp.com/cpp-tutorial/stdunique_ptr/

https://www.learncpp.com/cpp-tutorial/circular-dependency-issues-with-stdshared_ptr-and-stdweak_ptr/

https://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used

https://stackoverflow.com/questions/12030650/when-is-stdweak-ptr-useful

https://en.wikipedia.org/wiki/Dangling_pointer

https://stackoverflow.com/questions/16711697/is-there-any-use-for-unique-ptr-with-array

https://stackoverflow.com/questions/37884728/does-c11-unique-ptr-and-shared-ptr-able-to-convert-to-each-others-type

《Effective Modern C++》

扫码_搜索联合传播样式-白色版 1