实现智能指针 - 使用模板进行动态分配

3
我正在编写一个智能指针countedptr,但遇到了一些问题。countedptr的基本功能是像其他智能指针一样工作,并且具有指向单个对象的指针数量计数。目前,代码如下:

[已解决]

#include "std_lib_facilities.h"

template <class T>
class counted_ptr{
private:
    T* pointer;
    int* count;

public:
    counted_ptr(T* p = 0, int* c = new int(1)) : pointer(p), count(c) {}    // default constructor
    explicit counted_ptr(const counted_ptr& p) : pointer(p.pointer), count(p.count) { ++*count; } // copy constructor
    ~counted_ptr() { --*count; delete pointer; }

    counted_ptr& operator=(const counted_ptr& p)
    {
        pointer = p.pointer;
        count = p.count;
        ++*count;
        return *this;
    }
    T* operator->() const{ return pointer; }
    T& operator*() const { return *pointer; }

    int Get_count() const { return *count; }
};


int main()
{
    counted_ptr<double> one;
    counted_ptr<double>two(one);
    int a = one.Get_count();
    cout << a << endl;
}

当我尝试执行以下操作时:

one->pointer = new double(5);

然后我收到一个编译器错误,说“请求访问非类类型double的'*(&amp;one) -> counted_ptr :: operator-&gt; with T = double'中的成员'pointer'。”

我考虑制作一个函数来解决这个问题,虽然我可以创建一个函数来分配T类型的数组,但我想不出一种分配实际对象的方法。感谢任何帮助。


我认为 int *count 应该是静态的。不是吗? - KV Prajapati
我会尝试一下,看看是否可以使用"one = new double(5)"。 - trikker
静态成员在构造函数中的初始化会产生问题。 - trikker
当您给 counted_ptr 提供一个新的指针以保存时,您是否希望其他的 counted_ptr 指向新指针还是旧指针? - outis
现在它们指向旧的。我将实现它们指向新的。 - trikker
6个回答

5

旧解决方案

另一个赋值运算符怎么样?

counted_ptr& counted_ptr::operator=(T* p)
{
    if (! --*count) { delete count; }
    pointer = p;
    count = new int(1);
    return *this;
}

...

one = new double(5);

此外,您的析构函数总是删除一个共享指针,这可能是导致“one”成为随机数字的原因。也许您想要这样做:
counted_ptr::~counted_ptr() { if (! --*count) { delete pointer; delete count; } }

新解决方案

如果您想重新定位一个 counted_ptr(例如 one = new double(5))以更新所有相关的 counted_ptr,请将指针和计数都放在一个辅助类中,并让您的指针类持有一个指向该辅助类的指针(您可能已经朝着这个方向前进了)。您可以采用两种方式来完善这个设计:

  1. 将辅助类设计为简单的结构体(和一个私有内部类),并将所有逻辑放在外部类方法中。
  2. counted_ptr 设计为辅助类。counted_ptr 维护引用计数,但不会自动更新计数;它不是智能指针,只响应 releaseretain 消息。如果您对 Objective-C 有所了解,这基本上是其传统的内存管理方式(除了 autoreleasing)。当引用计数达到 0 时,counted_ptr 可能会自行删除(这是与 Obj-C 的另一个潜在区别)。不能复制 counted_ptr。意图是对于任何普通指针,最多只应该有一个 counted_ptr

    创建一个 smart_ptr 类,它具有指向 counted_ptr 的指针,这个指针在应该持有相同普通指针的 smart_ptr 实例之间共享。 smart_ptr 负责通过发送其 counted_ptr 的释放和保留方法来自动更新计数。

    counted_ptr 可能是 shared_ptr 的一个私有内部类,也可能不是。

这是选项二的接口。由于您正在进行练习,我将让您填写方法定义。潜在的实现方式与已发布的类似,但是counted_ptr不需要复制构造函数和复制赋值运算符,counted_ptr::~counted_ptr不调用counted_ptr::release(这是smart_ptr::~smart_ptr的工作),而counted_ptr::release可能不会释放counted_ptr::_pointer(您可以将其留给析构函数)。
// counted_ptr owns its pointer an will free it when appropriate.
template <typename T>
class counted_ptr {
private:
    T *_pointer;
    size_t _count;

    // Make copying illegal
    explicit counted_ptr(const counted_ptr&);
    counted_ptr& operator=(const counted_ptr<T>& p);

public:
    counted_ptr(T* p=0, size_t c=1);
    ~counted_ptr();

    void retain();        // increase reference count.
    bool release();       // decrease reference count. Return true iff count is 0
    void reassign(T *p);  // point to something else.
    size_t count() const;

    counted_ptr& operator=(T* p);

    T& operator*() const;
    T* operator->() const;
};

template <typename T>
class smart_ptr {
private:
    counted_ptr<T> *_shared;
    void release();  // release the shared pointer
    void retain();   // retain the shared pointer

public:
    smart_ptr(T* p=0, int c=1);   // make a smart_ptr that points to p
    explicit smart_ptr(counted_ptr<T>& p); // make a smart_ptr that shares p
    explicit smart_ptr(smart_ptr& p); // copy constructor
    ~smart_ptr();

    // note: a smart_ptr's brethren are the smart_ptrs that share a counted_ptr.
    smart_ptr& operator=(smart_ptr& p); /* Join p's brethren. Doesn't alter pre-call
        * brethren. p is non-const because this->_shared can't be const. */
    smart_ptr& operator=(counted_ptr<T>& p);  /* Share p. Doesn't alter brethren. 
        * p is non-const because *this isn't const. */
    smart_ptr& operator=(T* p); // repoint this pointer. Alters brethren

    size_t count() const; // reference count

    T& operator*() const;  // delegate these to _shared
    T* operator->() const;

};

希望以上唯一不明确的点是有意为之的。

我考虑过这个,但现在我打算坚持使用成员函数。不过,我会尝试你的析构函数更改,但我认为那不是问题所在,因为其中一个析构函数从未被调用过。 - trikker
更改析构函数成功了。谢谢。现在不需要成员函数了。 - trikker
那个赋值运算符的第一行应该模仿析构函数:如果引用计数降到零,它也应该delete pointer;否则,先前分配的任何内存可能会泄漏。 - TheUndeadFish
您是否指的是复制赋值操作符?因为我已经修复了析构函数,所以不需要使用上述操作符来分配内存。 - trikker
我的原始解决方案需要进行一些更新,基本上就变成了Managu的解决方案。@trikker:如果以上是你想要的,请接受那个解决方案。 - outis

2

抱歉,我是新手,不能留下评论。Adatapost提到的"one=new double(5);"应该可以工作。不过还需要做一个修改:引用计数需要一些帮助。

...
~counted_ptr() {
    --*count; 
    // deallocate objects whose last reference is gone.
    if (!*count) 
    {   
        delete pointer;
        delete count;
    }
}

counted_ptr& operator=(const counted_ptr& p)
{
    // be careful to accommodate self assignment
    ++*p.count;

    // may lose a reference here
    --*count;
    if (!*count)
    {
        delete pointer;
        delete count;
    }

    count=p.count;
    pointer=p.pointer;
    return *this;
}

当然,这里存在一些代码重复。将该代码重构为其自己的函数可能是有意义的,例如:
private:
    /** remove our reference */
    void release()
    {
        --*count;
        if (!*count)
        {
            delete pointer;
            delete count;
        }
    }

one = new double(5) 可以编译通过,但当程序运行并尝试输出 *one 时,你会得到一个疯狂的值。 - trikker

1
你是不是想说 "one.pointer=new double(5);"?写成 "one->pointer=new double(5);" 会调用 counted_ptr<double>::operator->。也就是说,大致等同于:
double *tmp = one.operator->(); // returns one.pointer
tmp->pointer = new double(5);

然而,双指针不是结构体,因此它没有一个名为pointer的成员。


一个指针无法工作,因为指针是私有的。这导致需要使用公共访问器方法或创建成员函数来进行分配。 - trikker

1

既然眼前的问题已经解决了,我想提供一些更长远的建议:

随着您继续开发这段代码,您肯定希望将其提交给有经验的程序员进行全面审查,无论是在这里还是其他地方。尽管outis已经帮助您纠正了一些明显的问题,但即使您的代码全部编译并在自己的测试中似乎工作正常,仍可能存在您尚未学会考虑的测试和情况。智能指针很容易出现微妙的问题,直到非常特定的情况才会显现出来。因此,您需要让其他人检查您的代码,以找出您可能错过的任何内容。

请不要把这视为对您当前代码的任何侮辱。我只是提供这个友好的建议,以确保您从这个项目中学到最多的东西。


我并不完全明白你的意思。这只是一个自学练习,但我从中学到的任何东西都是有帮助的。批评总是受欢迎的,只要不是贬低性的。 - trikker

0

除非你是出于学术目的,否则你可能想考虑使用boost::shared_ptruse_count()成员。它并不完全高效,但它确实可行,而且最好使用一些经过充分测试、成熟和线程安全的东西。如果你是为了学习目的而这样做,请务必查看More Effective C++中对引用计数和智能指针的处理。


我只是为了自学而做这个练习。实际上,我已经订购了《Effective C++》(不是“更多”,但我稍后会得到它)。 - trikker

0

在重载运算符=之前,您需要递减计数并可能删除对旧值的指针。您还需要在所有使用“delete pointer”的地方使用“delete count”,以避免内存泄漏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接