【问题标题】:Simple reference counting: smart pointers简单的引用计数:智能指针
【发布时间】:2010-08-31 21:18:17
【问题描述】:

我想使用智能指针实现一个简单的引用计数。变量pointer表示指向存储对象的指针,reference_count表示对象的副本总数。

  • 如果我们使用 NULL 初始化对象:reference_count = -1 else reference_count = 1
  • 复制 ctor 和 operator = 增加 reference_count
  • 析构函数递减reference_count,如果没有其他对指向对象的引用,则执行其删除。

这是我的代码:

#ifndef smart_pointer_H
#define smart_pointer_H

template < typename T > class smart_pointer
{
    private:
        T*    pointer;      
        int reference_count;    

    public:

        smart_pointer() : pointer(0), reference_count(-1) {}

        smart_pointer(T* p) : pointer(p)
        {
            if (p != NULL)
            {
                this->reference_count = 1;
            }

            else
            {
                this->reference_count = -1;
            }
        }

        smart_pointer(const smart_pointer <T> & p) : pointer(p.pointer),     reference_count(p.reference_count + 1) {}
        bool operator == (const smart_pointer <T>& p) { return pointer == p.pointer; }
        bool operator != (const smart_pointer <T>& p) { return pointer != p.pointer; }


        ~ smart_pointer()
        {
            if(-- reference_count == 0)
        {
                std::cout << "Destructing: " << '\n';
                delete pointer;
            }
        }

        T& operator *  () { return *pointer; }
        T* operator -> () { return pointer; }

        smart_pointer <T> & operator = (const smart_pointer <T> & p)
        {
                if (this != &p)
                {
                    if( -- reference_count == 0)
                    {
                        delete pointer;
                    }

                        pointer = p.pointer;
                        reference_count = p.reference_count + 1;
                }

        return *this;
        }
};    

这是我的测试代码,类示例存储 2D 点和两个指向任何其他 2D 点的指针。

template < typename T >
class smart_pointer;

class Point
{
private:
    double x, y;
    smart_pointer <Point> p1;
    smart_pointer <Point> p2;

public:
    Point(double xx, double yy): x(xx), y(yy) {this-> p1 = NULL; this->p2 = NULL;}
    Point(double xx, double yy, smart_pointer <Point> p1, smart_pointer <Point> p2): x(xx), y(yy) {this-> p1 = p1, this->p2 = p2; }
    double getX(){ return x;}
    double getY(){ return y;}
    void setX(double xx)  {this->x = xx;}
    void setY(double yy)  {this->y = yy;}
    void setP1(smart_pointer <Point> p1) {this->p1 = p1;}
    void setP2(smart_pointer <Point> p2) {this->p2 = p2;}

    void print()
    {
         std::cout << "x= " << x << " y= " << y << '\n';
         std::cout << "p1" << '\n';
         if (p1 != NULL)
         {
             p1->print();
         }
         std::cout << "p2" << '\n';
         if (p2 != NULL)
         {
            p2->print();
         }
         std::cout << '\n';
    }

};

二维点列表:

#include "Point.h"

class PointsList
{
private:
    std::vector <smart_pointer <Point> > points;

public:
    smart_pointer <Point> & operator [] ( int index ) {return points[index];}

public:
    void push_back(smart_pointer <Point> p) {points.push_back(p);}
    void erase(unsigned int index) {points.erase(points.begin() += index );}
    void printPoints()
    {
        std::cout << "List of points" << '\n';
        for (unsigned int i = 0; i < points.size();  i++)
        {
            points[i]->print();

        }

    }
};

测试代码:

#include "Point.h"
#include "PointsList.h"

int main()
{
    smart_pointer <Point> pb = NULL;
    pb = (new Point(0,0));
    smart_pointer <Point> p0(new Point(0,0));
    p0->print();
    smart_pointer <Point> p1(new Point(10,10));
    p1->print();
    smart_pointer <Point> p2(new Point(20,20));
    p2->print();
    smart_pointer <Point> p3(new Point(30,30));
    p3->print();

    smart_pointer <Point> pa(p3);
    p0->setP1(p2);
    p0->setP2(p3);
    p0->print();    
    p0 = p1;
    p0->print();
    p0->print();

    PointsList pl1;
    pl1.push_back(p0);
    pl1.push_back(p1);

    PointsList pl2;
    pl2.push_back(p2);
    pl2.push_back(p3);
    pl1.erase(0);
    pl1.printPoints();
    pl2.printPoints();
    return 0;
}

这种解决方案的优点或缺点在哪里?大量数据的运行速度、转换、继承可能出现的问题等如何。感谢您的帮助。

我对这个示例还有一个问题:哪种类型的智能指针(共享、作用域)最适合这种数据结构:

//Class with cross-references to points p1, p2
class PointTopo
{
private:
    double x, y;
    PointTopo * p1;
    Point * p2;

public:
    PointTopo(double xx, double yy): x(xx), y(yy) {this-> p1 = NULL; this->p2 = NULL;}
    ...

};

//Class  with cross references: topological model for Delaunay triangulation
class Edge
{
   private:
      Point2D * start;
      Edge *next;
      Edge *previous;
      Edge *twin;
...
};

感谢您的帮助...

【问题讨论】:

  • 我们可以假设这只是一个帮助您理解的练习吗?
  • 附言。你忘记了复制构造函数。
  • 是的,这只是循环引用测试的一个例子,但将来我想在我的应用程序中实现这样的智能指针。
  • 自己实现是个坏主意。获得正确的智能指针比看起来要困难得多。请使用由经过良好同行评审的库(C++0x std::shared_ptr/C++03 boost::shared_ptr 或 std::tr1::shared_ptr)提供的库。他们花了很长时间才解决了这些智能指针,但它们现在工作得很好。
  • 我什至从未注意到共享指针是新标准 C++0x 的一部分...... C++0x std::shared_ptr 和 std::tr1::shared_ptr 之间有什么区别吗?

标签: c++ templates smart-pointers reference-counting


【解决方案1】:

您的引用计数不起作用。

如果将两个智能指针复制或分配在一起,它们需要使用相同的位置来执行计数。

目前每个对象都有自己的计数,因此它们可能会变得不同步。

smart_pointer<int>  x(new x);      // x.pointer: <good> x.reference_count: 1
{
    smart_pointer<int>  y;         // y.pointer: NULL   y.reference_count: -1

    y = x;  // x.pointer: <good> x.reference_count: 1
            // y.pointer: <good> y.reference_count: 2

    smart_pointer<int>  z;
    x = z;  // x.pointer: NULL                        x.reference_count:  0 (BAD)
            // z.pointer: NULL                        z.reference_count: -1
            // y.pointer: <bad> (it was deleted by x) y.reference_count:  2
}

编辑:

按照 cmets 的要求说明问题。

到此为止。我们刚刚创建 z 的地方。但还没有完成x = z;

x { pointer: 0xabcd1234  reference_count: 1  }
y { pointer: 0xabcd1234  reference_count: 2  }
z { pointer: NULL        reference_count: -1 }


    // So here is your assignment operator.
    // Doing the `x = z` we will walk through the following code.
    //
    smart_pointer <T> & operator = (const smart_pointer <T> & p)
    {
            if (this != &p)
            {
                // We get here.
                // Left hand side is 'x' so this condition will be true.
                if( -- reference_count == 0)
                {
                    // Now we are deleting a pointer.
                    // That is held by 'x'
                    delete pointer;

                    // But 'y' is holding a pointer with the same value.
                    // Now y is holding a pointer to a deleted variable.
                }

                // Here we copy 'z' into 'x'
                // Copy the pointer. That happens to be NULL.
                pointer = p.pointer;

                // Now we copy and increment the reference count.
                // So 'x' has a value of 0 while 'z' has a value of -1.
                // This also breaks the invariant on 'x' that NULL values should
                // have a reference count of -1 (as X is NULL and ref-count is 0)
                reference_count = p.reference_count + 1;
            }

    return *this;
    }

如果有人尝试使用 'y' 现在我们有未定义的行为,因为它包含一个指向已释放内存的指针。

编辑经典(但过于简单的智能指针:

#include <vector>

template<typename T>
class SP
{
    T*        object;
    size_t*   count;

    public:
        SP(T* data)
        try
        // Use weird try around initializer list to catch new throwing.
        // If it does we delete data to stop it from leaking.
           :object(data)
           ,count(data ? new int(1) : NULL)
        { /* This is the constructor */}
        catch(...)
        {delete data;}

        SP():                 object(NULL),       count(NULL)       {}
      //SP(T* data):          object(data),       count(new int(1)) {}  // Lined up here so it look neat but implemented above to use weird try catch
        SP(SP<T> const& rhs): object(rhs.object), count(rhs.count)  {if (count) {++(*count);}}
        SP<T>& operator=(SP<T> rhs)  // Note implicit copy construction in rhs
        {
            // Using copy swap idiom for assignment.
            // The copy is hidden because the parameter is pass by value.
            this->swap(rhs);
            return *this;
        }
        void swap(SP<T>& rhs) throw()
        {
            std::swap(object, rhs.object);
            std::swap(count,  rhs.count);
        }
        ~SP()
        {
            if ((count) && (--(*count) == 0))
            {
                 delete count;
                 delete object;
            }
        }
};

【讨论】:

  • 感谢您的评论。但是我找不到问题,也许我没有正确理解问题。智能指针 z; x = 空; //x.ref_count = 0,不会在析构函数中删除 => 没有内存泄漏。
  • 使用新的复制构造函数可以在您的代码中插入相同类型的漏洞。
  • +1:引用计数的最基本原则是每个对象都有自己的引用计数。您将它们保存在每个指针中 - 因此,当任何给定的指针更新引用计数时,其他指针不会得到通知。
  • @DeadMG:好的。每个智能指针必须有引用对象的列表,并且在更改其状态后更新列表中的所有对象...
  • @Ian:我认为 DeadMG 的意思是:每个动态对象(不是智能指针)都有自己的引用计数。因此,当您分配智能指针时,您需要在内部进行两次分配。 1)动态对象。 2) 动态对象计数。现在两个智能指针都使用相同的计数器。所以你只需要增加它。
【解决方案2】:

我相信你做得很好,但为什么不使用boostloki 中提供的智能指针呢?两者都是多年精炼的产物。

【讨论】:

  • 即使忽略他的代码和 Martin 的 cmets,我也会高度怀疑他是否做得很好。出色地实现智能指针确实很困难。
  • 我遇到的人很少,甚至可以考虑所有潜在问题(我不是那些有天赋的少数人之一)。甚至 Scott Myers(以 C++ 闻名)也尝试在出版物中实现智能指针,但十年后他仍然收到错误报告。 (或者他在为本地 C++ 用户组做的一次演讲中是这么说的)。因此,如果 Scott Myers 无法正确执行此操作,那么我肯定不会尝试。
  • @Brian:我试图对他“轻松一点”。 :)
  • @Martin:当我说“忽略 Martin 的 cmets”时,我的意思是直接无视你说他的代码是错误的。我认为 Ian 的代码被破坏完全是因为他试图实现智能点这一事实,而不是基于对他的代码的任何了解,这是理所当然的。 IE。我是说,“这太难了,我就假设你犯了一个错误。我不需要实际检查你的代码来看看它是否有效;它肯定在某个地方有致命的错误。”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
  • 2017-03-20
  • 1970-01-01
  • 2010-10-18
  • 2016-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多