智能指针Concept

C++采用new 和 delete来进行堆内存的申请和释放,其中便存在潜在的内存泄漏的问题。
比如下面这个例子

  1. #include <iostream>
  2. using namespace std;
  3. class Rectangle {
  4. private:
  5. int length;
  6. int breadth;
  7. };
  8. void fun()
  9. {
  10. // By taking a pointer p and
  11. // dynamically creating object
  12. // of class rectangle
  13. Rectangle* p = new Rectangle();
  14. }
  15. int main()
  16. {
  17. // Infinite Loop
  18. while (1) {
  19. fun();
  20. }
  21. }

由于析构函数具有在object离开作用域自动被调用的特性,因此可以考虑采用class来管理指针,同时重载预算符和实现析构函数来完成智能指针。

Using Smart Pointers, we can make pointers to work in a way that we don’t need to explicitly call delete. A smart pointer is a wrapper class over a pointer with an operator like and -> overloaded. The objects of smart pointer class look like a pointer but can do many things that a normal pointer can’t like automatic destruction (yes, we don’t have to explicitly use delete), reference counting and more. The idea is to take a class with a pointer, destructor and overloaded operators like and ->. Since the destructor is automatically called when an object goes out of scope, the dynamically allocated memory would automatically be deleted (or reference count can be decremented). Consider the following simple smart ptr class.

如下是一个简单的智能指针的实现

  1. #include <iostream>
  2. using namespace std;
  3. class SmartPtr {
  4. int* ptr; // Actual pointer
  5. public:
  6. // Constructor: Refer https:// www.geeksforgeeks.org/g-fact-93/
  7. // for use of explicit keyword
  8. explicit SmartPtr(int* p = NULL) { ptr = p; }
  9. // Destructor
  10. ~SmartPtr() { delete (ptr); }
  11. // Overloading dereferencing operator
  12. int& operator*() { return *ptr; }
  13. };
  14. int main()
  15. {
  16. SmartPtr ptr(new int());
  17. *ptr = 20;
  18. cout << *ptr;
  19. // We don't need to call delete ptr: when the object
  20. // ptr goes out of scope, the destructor for it is automatically
  21. // called and destructor does delete ptr.
  22. return 0;
  23. }

采用template来实现通用的smart pointer

  1. #include <iostream>
  2. using namespace std;
  3. // A generic smart pointer class
  4. template <class T>
  5. class SmartPtr {
  6. T* ptr; // Actual pointer
  7. public:
  8. // Constructor
  9. explicit SmartPtr(T* p = NULL) { ptr = p; }
  10. // Destructor
  11. ~SmartPtr() { delete (ptr); }
  12. // Overloading dereferncing operator
  13. T& operator*() { return *ptr; }
  14. // Overloading arrow operator so that
  15. // members of T can be accessed
  16. // like a pointer (useful if T represents
  17. // a class or struct or union type)
  18. T* operator->() { return ptr; }
  19. };
  20. int main()
  21. {
  22. SmartPtr<int> ptr(new int());
  23. *ptr = 20;
  24. cout << *ptr;
  25. return 0;
  26. }

C++智能指针

unique_ptr独占指针

If you are using a unique pointer then if one object is created and pointer P1 is pointing to this one them only one pointer can point this one at one time. So we can’t share with another pointer, but we can transfer the control to P2 by removing P1.

采用uique_ptr表明同一时间只能有一个指针使用所创建出来的object。即实现独占式拥有(exclusive ownership)或严格拥有(strict ownership)概念,保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。它对于避免内存泄漏(resource leak)——如 new 后忘记 delete ——特别有用。

独享所有权的智能指针,资源只能被一个指针占有,该指针不能拷贝构造和赋值。但可以进行移动构造和移动赋值构造(调用 move() 函数),即一个 unique_ptr 对象赋值给另一个 unique_ptr 对象,可以通过该方法进行赋值。

image.png

#include <iostream> 
using namespace std; 
#include <memory> 

class Rectangle { 
    int length; 
    int breadth; 

public: 
    Rectangle(int l, int b) 
    { 
        length = l; 
        breadth = b; 
    } 

    int area() 
    { 
        return length * breadth; 
    } 
}; 

int main() 
{ 

    unique_ptr<Rectangle> P1(new Rectangle(10, 5)); 
    cout << P1->area() << endl; // This'll print 50 

    // unique_ptr<Rectangle> P2(P1); 

    unique_ptr<Rectangle> P2; 
    P2 = move(P1); 

    // This'll print 50 
    cout << P2->area() << endl; 

    // cout<<P1->area()<<endl; 
    return 0; 
}

Example2:

// C++ program to illustrate the use of unique_ptr 
#include <iostream> 
#include <memory> 
using namespace std; 

class A { 
public: 
    void show() 
    { 
        cout << "A::show()" << endl; 
    } 
}; 

int main() 
{ 
    unique_ptr<A> p1(new A); 
    p1->show(); 

    // returns the memory address of p1 
    cout << p1.get() << endl; 

    // transfers ownership to p2 
    unique_ptr<A> p2 = move(p1); 
    p2->show(); 
    cout << p1.get() << endl; 
    cout << p2.get() << endl; 

    // transfers ownership to p3 
    unique_ptr<A> p3 = move(p2); 
    p3->show(); 
    cout << p1.get() << endl; 
    cout << p2.get() << endl; 
    cout << p3.get() << endl; 

    return 0; 
} 

//Output
A::show()
0x1c4ac20
A::show()
0          // NULL
0x1c4ac20
A::show()
0          // NULL
0          // NULL
0x1c4ac20

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another.

shared_ptr共享指针

If you are using shared_ptr then more than one pointer can point to this one object at a time and it’ll maintain a Reference Counter using use_count() method.
**
多个智能指针可以共享同一个对象,对象的最末一个拥有着有责任销毁对象,并清理与该对象相关的所有资源。

  • 支持定制型删除器(custom deleter),可防范 Cross-DLL 问题(对象在动态链接库(DLL)中被 new 创建,却在另一个 DLL 内被 delete 销毁)、自动解除互斥锁

**

资源可以被多个指针共享,使用计数机制表明资源被几个指针共享。通过 use_count() 查看资源的所有者的个数,可以通过 unique_ptr、weak_ptr 来构造,调用 release() 释放资源的所有权,计数减一,当计数减为 0 时,会自动释放内存空间,从而避免了内存泄漏。

image.png

#include <iostream> 
using namespace std; 
#include <memory> 

class Rectangle { 
    int length; 
    int breadth; 

public: 
    Rectangle(int l, int b) 
    { 
        length = l; 
        breadth = b; 
    } 

    int area() 
    { 
        return length * breadth; 
    } 
}; 

int main() 
{ 

    shared_ptr<Rectangle> P1(new Rectangle(10, 5)); 
    // This'll print 50 
    cout << P1->area() << endl; 

    shared_ptr<Rectangle> P2; 
    P2 = P1; 

    // This'll print 50 
    cout << P2->area() << endl; 

    // This'll now not give an error, 
    cout << P1->area() << endl; 

    // This'll also print 50 now 
    // This'll print 2 as Reference Counter is 2 
    cout << P1.use_count() << endl; 
    return 0; 
} 
/*
Output
50
50
50
2
*/

example2:

// C++ program to demonstrate shared_ptr 
#include <iostream> 
#include <memory> 
using namespace std; 

class A { 
public: 
    void show() 
    { 
        cout << "A::show()" << endl; 
    } 
}; 

int main() 
{ 
    shared_ptr<A> p1(new A); 
    cout << p1.get() << endl; 
    p1->show(); 
    shared_ptr<A> p2(p1); 
    p2->show(); 
    cout << p1.get() << endl; 
    cout << p2.get() << endl; 

    // Returns the number of shared_ptr objects 
    // referring to the same managed object. 
    cout << p1.use_count() << endl; 
    cout << p2.use_count() << endl; 

    // Relinquishes ownership of p1 on the object 
    // and pointer becomes NULL 
    p1.reset(); 
    cout << p1.get() << endl; 
    cout << p2.use_count() << endl; 
    cout << p2.get() << endl; 

    return 0; 
} 

//Output
0x1c41c20
A::show()
A::show()
0x1c41c20
0x1c41c20
2
2
0          // NULL
1
0x1c41c20

Use shared_ptr if you want to share ownership of a resource. Many shared_ptr can point to a single resource. shared_ptr maintains reference count for this propose. when all shared_ptr’s pointing to resource goes out of scope the resource is destroyed.

当你期望让多个指针共同拥有对于某一个资源的管理权限时,可以采用shared_ptr, shared_ptr采用一个引用计数器来判断有多少个指针管理这个资源,当引用计数器为0的时候,就会将资源释放掉。

实现的示例

#include <iostream>
#include <memory>

template <typename T>
class SmartPtr
{
private : 
    T *_ptr;
    size_t *_count;

public:
    SmartPtr(T *ptr = nullptr) : _ptr(ptr)
    {
        if (_ptr)
        {
            _count = new size_t(1);
        }
        else
        {
            _count = new size_t(0);
        }
    }

    ~SmartPtr()
    {
        (*this->_count)--;
        if (*this->_count == 0)
        {
            delete this->_ptr;
            delete this->_count;
        }
    }

    SmartPtr(const SmartPtr &ptr) // 拷贝构造:计数 +1
    {
        if (this != &ptr)
        {
            this->_ptr = ptr._ptr;
            this->_count = ptr._count;
            (*this->_count)++;
        }
    }

    SmartPtr &operator=(const SmartPtr &ptr) // 赋值运算符重载 
    {
        if (this->_ptr == ptr._ptr)
        {
            return *this;
        }
        if (this->_ptr) // 将当前的 ptr 指向的原来的空间的计数 -1
        {
            (*this->_count)--;
            if (this->_count == 0)
            {
                delete this->_ptr;
                delete this->_count;
            }
        }
        this->_ptr = ptr._ptr;
        this->_count = ptr._count;
        (*this->_count)++; // 此时 ptr 指向了新赋值的空间,该空间的计数 +1
        return *this;
    }

    T &operator*()
    {
        assert(this->_ptr == nullptr);
        return *(this->_ptr);
    }

    T *operator->()
    {
        assert(this->_ptr == nullptr);
        return this->_ptr;
    }

    size_t use_count()
    {
        return *this->count;
    }
};

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e4thgm/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

shared_ptr本身实例上是线程安全的,但是对于所指向的内容不是线程线程安全的,因为它不是用来进行并行管理的。并行管理需要自己通过锁来实现。

The thread-safety of the shared_ptr<> instances only applies to managing shared_ptr<> instances which were initialized from each other, not what they’re pointing to.

The contents of the shared_ptr are not thread-safe, nor is writing to the same shared_ptr instance.


A shared_ptr<> is a mechanism to ensure that multiple object owners ensure an object is destructed, not a mechanism to ensure multiple threads can access an object correctly. You still need a separate synchronization mechanism to use it safely in multiple threads (like std::mutex).

可能存在的问题

智能指针可能存在的问题是循环引用。循环引用造成了该被调用的析构函数没有被调用,从而造成了内存泄漏。
在如下例子中定义了两个类 Parent、Child,在两个类中分别定义另一个类的对象的共享指针,由于在程序结束后,两个指针相互指向对方的内存空间,导致内存无法释放。

#include <iostream>
#include <memory>

using namespace std;

class Child;
class Parent;

class Parent {
private:
    shared_ptr<Child> ChildPtr;
public:
    void setChild(shared_ptr<Child> child) {
        this->ChildPtr = child;
    }

    void doSomething() {
        if (this->ChildPtr.use_count()) {

        }
    }

    ~Parent() {
    }
};

class Child {
private:
    shared_ptr<Parent> ParentPtr;
public:
    void setPartent(shared_ptr<Parent> parent) {
        this->ParentPtr = parent;
    }
    void doSomething() {
        if (this->ParentPtr.use_count()) {

        }
    }
    ~Child() {
    }
};

int main() {
    weak_ptr<Parent> wpp;
    weak_ptr<Child> wpc;
    {
        shared_ptr<Parent> p(new Parent);
        shared_ptr<Child> c(new Child);
        p->setChild(c);
        c->setPartent(p);
        wpp = p;
        wpc = c;
        cout << p.use_count() << endl; // 2
        cout << c.use_count() << endl; // 2
    }
    cout << wpp.use_count() << endl;  // 1
    cout << wpc.use_count() << endl;  // 1
    return 0;
}

循环引用的解决方法是采用weak_ptr

weak_ptr 对被 shared_ptr 管理的对象存在 非拥有性(弱)引用,在访问所引用的对象前必须先转化为 shared_ptr;
weak_ptr 用来打断 shared_ptr 所管理对象的循环引用问题,若这种环被孤立(没有指向环中的外部共享指针),shared_ptr 引用计数无法抵达 0,内存被泄露;令环中的指针之一为弱指针可以避免该情况;
weak_ptr 用来表达临时所有权的概念,当某个对象只有存在时才需要被访问,而且随时可能被他人删除,可以用 weak_ptr 跟踪该对象;需要获得所有权时将其转化为 shared_ptr,此时如果原来的 shared_ptr 被销毁,则该对象的生命期被延长至这个临时的 shared_ptr 同样被销毁。

#include <iostream>
#include <memory>

using namespace std;

class Child;
class Parent;

class Parent {
private:
    //shared_ptr<Child> ChildPtr;
    weak_ptr<Child> ChildPtr;
public:
    void setChild(shared_ptr<Child> child) {
        this->ChildPtr = child;
    }

    void doSomething() {
        //new shared_ptr
        if (this->ChildPtr.lock()) {

        }
    }

    ~Parent() {
    }
};

class Child {
private:
    shared_ptr<Parent> ParentPtr;
public:
    void setPartent(shared_ptr<Parent> parent) {
        this->ParentPtr = parent;
    }
    void doSomething() {
        if (this->ParentPtr.use_count()) {

        }
    }
    ~Child() {
    }
};

int main() {
    weak_ptr<Parent> wpp;
    weak_ptr<Child> wpc;
    {
        shared_ptr<Parent> p(new Parent);
        shared_ptr<Child> c(new Child);
        p->setChild(c);
        c->setPartent(p);
        wpp = p;
        wpc = c;
        cout << p.use_count() << endl; // 2
        cout << c.use_count() << endl; // 1
    }
    cout << wpp.use_count() << endl;  // 0
    cout << wpc.use_count() << endl;  // 0
    return 0;
}

weak_ptr 弱指针

weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。因此,在 default 和 copy 构造函数之外,weak_ptr 只提供 “接受一个 shared_ptr” 的构造函数。

  • 可打破环状引用(cycles of references,两个其实已经没有被使用的对象彼此互指,使之看似还在 “被使用” 的状态)的问题,即可以防止死锁。

    It’s much more similar to shared_ptr except it’ll not maintain a Reference Counter.In this case, a pointer will not have a strong hold on the object. The reason is if suppose pointers are holding the object and requesting for other objects then they may form a Deadlock. 指向 share_ptr 指向的对象,能够解决由shared_ptr带来的循环引用问题。


image.png

Tips

使用shared_ptr来管理返回的指针

这个是我自己的想法:当遇到一个函数的返回是一个指针的时候,如果不确定其是否存在在函数体内只是new了资源并且返回了资源,从而存在潜在的内存泄漏问题,可以使用shared_ptr进行双保险。

#include <iostream>
#include <memory>

using namespace std;

class requestMemory {
    public:
    requestMemory(const string& name): placeholder(name) { cout << "apply memory: " << placeholder << endl;}
    ~requestMemory() { cout << "release memorty: " << placeholder << endl;;}

    string placeholder;
};

requestMemory* applyMemory(const string& str)
{
    return new requestMemory(str);
}

int main()
{
    requestMemory *ptr1 = applyMemory(string("case1"));
    delete ptr1;
    shared_ptr<requestMemory>ptr2(applyMemory(string("case2")));
    return 0;
}

//Output
apply memory: case1
release memorty: case1
apply memory: case2
release memorty: case2

Reference

https://www.geeksforgeeks.org/smart-pointers-cpp/