5

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

 1 year ago
source link: https://www.luozhiyun.com/archives/762
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近在整理一些 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

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK