share_ptr的几个注意点

作者: NickYang 分类: 技术文章,程序开发 发布时间: 2014-05-14 09:24

智能指针在boost中很早就有了,在tr1上也很早,但是没怎么用,后来0x标准出来之后,智能指针变成了标准库,所以现在用起来就不区分boost和std了。

主要说下share_ptr的几个注意点,待补全。

1.环状的链式结构可能会形成内存泄露
例如:

class BaseClass;
class ChildClass;

typedef std::shared_ptr<BaseClass> BaseClassPtr;
typedef std::shared_ptr<ChildClass> ChildClassPtr;

class BaseClass
{
public:
    ChildClassPtr childClass;
protected:
private:
};

class ChildClass
{
public:
    BaseClassPtr baseClass;
protected:
private:
};

int _tmain(int argc, _TCHAR* argv[])
{
    BaseClassPtr base(new BaseClass());
    ChildClassPtr child(new ChildClass());

    base->childClass = child;
    child->baseClass = base;


    system("pause");
    return 0;
}

2.多线程环境下使用代价更大。

因为share_ptr内部有两个数据成员,一个是指向对象的指针 ptr,另一个是 ref_count 指针,指向堆上的 ref_count 对象,读写操作不能原子化,(具体的结构图可以查看 陈硕的文章《为什么多线程读写 shared_ptr 要加锁?》)所以多线程下要么加锁,要么小心翼翼使用share_ptr。

例如:

class Test
{
public:
    Test() {}

    ~Test() {}

    // ...
protected:
private:
};

void func(std::shared_ptr test_ptr)
{
    // 大量使用test_ptr

    std::shared_ptr temp_ptr = test_ptr;
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr sp(new Test());
    boost::thread th1(std::bind(&func, sp));
    boost::thread th2(std::bind(&func, sp));

    th1.join();
    th2.join();

    return 0;
}

上面的代码不知道什么时候可能就宕了,而且不容易找到问题,这个时候你就得硬看代码了。

你也可以通过使用weak_ptr来解决这个问题,例如上述例子可以修改为:

class Test
{
public:
    Test() {}

    ~Test() {}

    // ...
protected:
private:
};

void func(std::weak_ptr test_ptr)
{
    // 大量使用test_ptr

    std::weak_ptr temp_ptr = test_ptr;
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr sp(new Test());
    std::weak_ptr wp(sp);

    boost::thread th1(std::bind(&func, wp));
    boost::thread th2(std::bind(&func, wp));

    th1.join();
    th2.join();

    return 0;
}

weak_ptr是一种可构造可赋值的不增加引用计数来管理share_ptr的智能指针,它可以非常方便的通过weak_ptr.lock()转为share_ptr,通过weak_ptr.expired()来判断智能指针是否被释放,还是非常方便的。条目1中的例子使用weak_ptr就可以解决问题

3.share_ptr包装this的时候使用enable_shared_from_this

class Test
{
public:
    Test() {}

    ~Test() {}

    std::shared_ptr get_ptr()
    {
        return std::shared_ptr(this);
    }

    // ...
protected:
private:
};

int _tmain(int argc, _TCHAR* argv[])
{
    Test t;
    std::shared_ptr t_ptr(t.get_ptr());
    return 0;
}

这样就会发生析构两次的问题,可以使用enable_shared_from_this来做共享,上面例子修改为

class Test : public std::enable_shared_from_this<Test>
{
public:
    Test() {}

    ~Test() {}

    std::shared_ptr get_ptr()
    {
        return shared_from_this();
    }

    // ...
protected:
private:
};

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr t_ptr(new Test());
    t_ptr->get_ptr();

    return 0;
}

4.share_ptr多次引用同一数据会导致内存多次释放

int _tmain(int argc, _TCHAR* argv[])
{
    int* int_ptr = new int[100];
    std::shared_ptr s_int_ptr1(int_ptr);

    // do something

    std::shared_ptr s_int_ptr2(int_ptr);

    return 0;
}

而且C++之父对share_ptr的初衷是:“shared_ptr用于表示共享拥有权。然而共享拥有权并不是我的初衷。在我看来,一个更好的办法是为对象指明拥有者并且为对象定义一个可以预测的生存范围。”

 

总结:智能指针虽然好,但是还需要谨慎的使用,C++的新特性很不错,但是写C++代码还是得非常注意,各位共勉吧

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

12条评论
  • adidos

    2015 年 3 月 2 日 10:48

    关于环状的链式结构中使用shared_ptr是不是可以考虑在其中一个结构体中使用weak_ptr解决,仅供参考

    1. eliteYang

      2015 年 3 月 2 日 12:20

      weak_ptr应该是可以解决,我没有试过,反正C++用这些东西还是得小心再小心

  • 果冻想

    2014 年 10 月 28 日 14:20

    > 3.share_ptr包装this的时候使用enable_shared_from_this晕死,好像这个小节下面的代码不能说明这个问题吧。Test t创建一个变量在栈上,然后使用shared_ptr去管理,本来就是有矛盾的。这种用法是在给自己找不自在。最后,这篇文章,很多地方还值得去商榷,对于初学者来说,可能有一定的误导。以上是我作为一个读者的理解。

    1. eliteYang

      2014 年 10 月 28 日 14:39

      这种做法是本来就是很奇怪,但是的确有(不少)人都是这么用的,enable_shared_from_this很多人忘了用这个,可以看下asio里面,大量在使用

  • 果冻想

    2014 年 10 月 28 日 12:25

    > 1.环状的链式结构可能会形成内存泄露示例代码有问题,对于以下typedef是错误的。typedef std::shared_ptr BaseClassPtr;typedef std::shared_ptr ChildClassPtr;是不是需要修改为:typedef std::shared_ptr < BaseClass > BaseClassPtr;typedef std::shared_ptr < ChildClass > ChildClassPtr;以上是我的意见,不知道文中的代码是否都已经经错了测试,至少应该在编译器上跑一遍吧。

    1. eliteYang

      2014 年 10 月 30 日 14:25

      应该是你说的那样的,代码均在VS下测试过,只不过拷贝到wordpress之后尖括号有时候会直接没掉,经常会出现#include 后没有头文件,已经改正

  • 唉小星星

    2014 年 7 月 25 日 17:30

    2.多线程环境下使用代价更大。
    例子举得有问题。那段代码是正确的。。

    1. eliteYang

      2014 年 7 月 25 日 17:37

      这个并不是必出问题的,偶尔会出,你可以看看上面提到的陈硕写的那个文章,里面有详细解说

  • Youth.霖

    2014 年 5 月 27 日 23:29

    为什么是_tmain()为主函数呢,只有在Windows下才能这样写是吗?我记得VS创建的非空C++工程就是_tmain

    1. eliteYang

      2014 年 5 月 28 日 09:52

      因为vs创建的控制台工程就是这样的

  • Yu

    2014 年 5 月 14 日 20:40

    我还以为只有boost有share_ptr来着

    表示不大敢用,偶尔用也都是每次都是先gdb看释放情况,完了才用两下
    还是自己手工delete那种放心

    1. eliteYang

      2014 年 5 月 15 日 16:21

      用小心点还是没什么问题的,只要像指针那样用,基本没什么问题

发表评论

电子邮件地址不会被公开。 必填项已用*标注