玩命加载中 . . .

智能指针


shared_ptr

shared_ptr会进行引用计数,引用计数为0时自动销毁

类似下面的实现

template<class T>
class shared_ptr {
private:
    T *p;
    size_t *use;
public:
    shared_ptr(T *s = nullptr): p(s) {
        use = new size_t();
        if (s) *use = 1;
    }
    shared_ptr(const shared_ptr<T>& lhs): p(lhs.p), use(lhs.use) {
        ++*use;
    }
    shared_ptr& operator=(const shared_ptr<T>& lhs) {
        if (this != &lhs) {
            ++*lhs.use;
            if (--*use < 1) {
                delete p;
                delete use;
            }
            p = lhs.p;
            use = lhs.use;
        }
        return *this;
    }
    size_t use_count() { return *use; }
    ~shared_ptr() {
        if (--*use < 1) {
            delete p;
            delete use;
        }
    }
};

具体应用

void ex3() {
    puts(">>>>>Entering ex3<<<<<");
    auto e1 = make_shared<Entity>();
    cout << e1.use_count() << endl;
    {
        puts("-----Entering scope-----");
        auto e2 = e1;
        cout << e1.use_count() << endl;
        auto e3 = move(e2);    // move是ownership转移
        cout << e1.use_count() << endl;
        puts("-----Leaving scope-----");
    }
    cout << e1.use_count() << endl;
    puts(">>>>>Leaving ex3<<<<<");
}
int main() {
    ex3();
    return 0;
}

>>>>>Entering ex3<<<<<
Entity created!
1
-----Entering scope-----
2
2
-----Leaving scope-----
1
>>>>>Leaving ex3<<<<<
Entity destroyed!

shared_ptr循环引用问题

class B;

class A {
public:
    A() { cout << "A constructor" << endl; }
    shared_ptr<B> b;
    void test() { cout << "A test" << endl; }
    ~A() { cout << "A destructor" << endl; }
};

class B {
public:
    B() { cout << "B constructor" << endl; }
    shared_ptr<A> a;
    void test() { cout << "B test" << endl; }
    ~B() { cout << "B destructor" << endl; }
};


int main() {
    shared_ptr<A> pa = make_shared<A>();
    shared_ptr<B> pb = make_shared<B>();
    pa->b = pb;
    pb->a = pa;
    cout << pa->b.use_count() << endl;
    cout << pb->a.use_count() << endl;
    return 0;
}
A constructor
B constructor
2
2   // 没有调用析构函数

这里pa指针指向的A对象中的指针指向B对象,pb指针指向的B对象中的指针指向A对象,所以A对象和B对象的引用计数都为2

程序结束时,pb指针先析构,这样B对象的引用计数减一变成1,还不能释放,pa指针再析构,这样A对象的引用计数减一变成1,也不能释放,所以就变成两个对象相互引用,都不能释放,导致内存泄漏

解决方法:声明对象时使用强指针shared_ptr,引用对象时使用弱指针weak_ptr

这里类A需要引用B对象,所以可以用weak_ptr替换shared_ptr,类B同理

class B;

class A {
public:
    A() { cout << "A constructor" << endl; }
    weak_ptr<B> b;  // 换成weak_ptr
    void test() { cout << "A test" << endl; }
    ~A() { cout << "A destructor" << endl; }
};

class B {
public:
    B() { cout << "B constructor" << endl; }
    weak_ptr<A> a;  // 换成weak_ptr
    void test() { cout << "B test" << endl; }
    ~B() { cout << "B destructor" << endl; }
};


int main() {
    shared_ptr<A> pa = make_shared<A>();
    shared_ptr<B> pb = make_shared<B>();
    pa->b = pb;
    pb->a = pa;
    cout << pa->b.use_count() << endl;
    cout << pb->a.use_count() << endl;
    return 0;
}
A constructor
B constructor
1
1
B destructor    // 调用B的析构函数
A destructor    // 调用A的析构函数

改成weak_ptr后,不会增加对象的引用计数,所以A对象只有指针pa引用,所以引用计数为1,B对象只有指针pb引用,所以引用计数也为1

指针pb析构时,B对象引用计数减一后变成0,调用析构函数,释放内存,指针pa析构时,A对象的引用计数减一后变成0,也调用析构函数,释放内存,这样就不会内存泄漏了

weak_ptr

  • 不具有普通指针的行为,没有重载operator*operator->
  • 没有共享资源,它的构造不会引起引用计数增加
  • 用于协助shared_ptr来解决循环引用问题
  • 可以从一个shared_ptr或者另外一个weak_ptr对象构造,进而可以间接获取资源的弱共享权

weak_ptr使用前检查对象是否还存在,确认存在才能使用

void observe(weak_ptr<Entity> ew) {
    puts("*****Entering observe*****");
    if (shared_ptr<Entity> spt = ew.lock()) {   // 如果对象可用,会用一个shared_ptr指向它
        cout << spt.use_count() << endl;    // 2
        cout << "entity still alive" << endl;
    } else {
        cout << "entity was expired" << endl;
    }
    puts("*****Leaving observe*****");
}

void ex4() {
    puts(">>>>>Entering ex4<<<<<");
    weak_ptr<Entity> ew;
    {
        puts("-----Entering scope-----");
        auto e1 = make_shared<Entity>();
        cout << e1.use_count() << endl; // 1
        ew = e1;
        cout << e1.use_count() << endl; // 1,引用计数没有增加
        observe(ew);
        puts("-----Leaving scope-----");
    }   // 离开之后共享指针对象就销毁了
    observe(ew);
    puts(">>>>>Leaving ex4<<<<<");
}

int main(int argc, char const *argv[]) {
    ex4();
    return 0;
}

>>>>>Entering ex4<<<<<
-----Entering scope-----
Entity created!
1
1
*****Entering observe*****
2
entity still alive
*****Leaving observe*****
-----Leaving scope-----
Entity destroyed!
*****Entering observe*****
entity was expired
*****Leaving observe*****
>>>>>Leaving ex4<<<<<

unique_ptr

采用独占式拥有,意味着可以确保一个对象和其相应的资源同一时间只被一个pointer拥有

类似下面的实现

template<class T> 
class unique_ptr {
private:
    T* p; 
public: 
    unique_ptr() :p() {}
    unique_ptr(T* s) :p(s) {}
    ~unique_ptr() { delete p; }

    unique_ptr(const unique_ptr&) = delete;     // 不能拷贝构造
    unique_ptr& operator=(const unique_ptr&) = delete;  // 不能赋值构造

    unique_ptr(unique_ptr&& s) :p(s.p) { s.p = nullptr } 

    unique_ptr& operator=(unique_ptr s) { 
        delete p; 
        p = s.p; 
        s.p = nullptr; 
        return *this; 
    } 

    T* operator->() const { return p; }
    T& operator*() const { return *p; }
}; 
  • 无法进行拷贝与赋值操作
unique_ptr<int> ptr(new int(1));
unique_ptr<int> ptr1(ptr);  // error
unique_ptr<int> ptr2 = ptr; // error
  • 显示的所有权转移(通过move语义)
unique_ptr<int> ptr(new int(1));
unique_ptr<int> ptr1 = move(ptr);  // ok
  • 作为容器元素存储在容器中
unique_ptr<int> ptr(new int(1));
vector<unique_ptr<int>> v;

v.push_back(ptr);   // error, 不能拷贝
v.push_back(move(ptr));    // ok, 可以移动

cout << *ptr << endl; // error, 移动之后就不能再用了
#include <iostream>
#include <memory>

using namespace std;

class Entity {
public:
    Entity() { cout << "Entity created!" << endl; }
    ~Entity() {cout << "Entity destroyed!" << endl; }
};

void ex1() {
    puts(">>>>>Entering ex1<<<<<");
    {
        puts("-----Entering scope-----");
        auto e1 = make_unique<Entity>();
        puts("-----Leaving scope-----");
    }
    puts(">>>>>Leaving ex1<<<<<");
}

int main() {
    ex1();
    return 0;
}

>>>>>Entering ex1<<<<<
-----Entering scope-----
Entity created!
-----Leaving scope-----
Entity destroyed!   // 离开scope作用域就被销毁了
>>>>>Leaving ex1<<<<<
  • Ownership转移到foo里面,所以离开foo之后就销毁了
    moveownership的直接转移,调用者不再拥有对象
void foo(unique_ptr<Entity>) {
    puts("-----Entering foo-----");
    puts("-----Leaving foo-----");
}

void ex2() {
    puts(">>>>>Entering ex2<<<<<");
    auto e1 = make_unique<Entity>();
    foo(move(e1));     // 移动拷贝
    puts(">>>>>Leaving ex2<<<<<");
}
int main() {
    ex2();
    return 0;
}

>>>>>Entering ex2<<<<<
Entity created!
-----Entering foo-----
-----Leaving foo-----
Entity destroyed!   // 对象转移到foo中,离开foo就被销毁了
>>>>>Leaving ex2<<<<<
  • unique_ptr禁止对指针进行复制和赋值
class Fish {
  public:
    Fish() {cout << "constructor" << endl;}
    ~Fish() {cout << "destructor" << endl;}

    void Swim() {
        cout << "Fish swim in water" << endl;
    }
};

void MakeFishSwim(const unique_ptr<Fish>& ptr) {
    ptr->Swim();
}

int main() {
    unique_ptr<Fish> pFish(new Fish);
    pFish->Swim();
    MakeFishSwim(pFish);
    return 0;
}
// constructor
// Fish swim in water
// Fish swim in water
// destructor

auto_ptr

已被C++11弃用

auto_ptrunique_ptr都是独占所有权的智能指针

类似于下面的实现

template<class T>
class auto_ptr {
    T* p;
public:
    auto_ptr(T* s) :p(s) {}
    ~auto_ptr() { delete p; }
     
    auto_ptr(auto_ptr& a) {
        p = a.p;
        a.p = NULL; // 源指针被置空
    }
    auto_ptr& operator=(auto_ptr& a) {
        delete p;
        p = a.p;
        a.p = NULL; // 源指针被置空
        return *this;
    }
    T& operator*() const { return *p; }
    T* operator->() const { return p; }
}; 

在使用auto_ptr进行拷贝构造和拷贝赋值的时候,会造成所有权的转移,导致源指针被置空,这样源指针就无法使用了,但这并不符合拷贝的语义,拷贝的时候不应该修改原来的对象

auto_ptr<classA> a(new classA);
auto_ptr<classA> b = a;
a->Method();

这里b=a的时候,指针a就被置空了,后面再调用对应方法就会出错了

另外也有很多限制

  • 不能在STL容器中使用,因为复制将导致数据无效
  • 一些STL算法也可能导致auto_ptr失效,比如sort算法
  • 不能作为函数参数,因为这会导致复制,并且在调用后,导致原数据无效
  • 如果作为类的成员变量,需要注意在类拷贝时候导致的数据无效

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录