试图弄清楚何时调用析构函数

Trying to figure out when destructors get called

本文关键字:调用 析构函数 何时 弄清楚      更新时间:2023-10-16

下面我有一个保持引用计数的类和一个封装指向另一个对象的指针的类。

当类Ptr不再附加任何对象时,我希望解除分配。这需要删除对象和引用计数。当ptrcnt的值达到零时,会发出信号。

我的Ptr_count类有一个析构函数,它完成自己的部分释放。我知道当Ptr的析构函数被调用时,这个析构函数会被调用,它会相应地释放内存。然而,当调用Ptr的赋值运算符并执行代码if(--refptr == 0) { delete p; }时,我不太确定它是否会被调用。

Ptr_count中有一段代码被注释掉了。如果我使用它而不是析构函数,那么每当refptr的值变为零时,就会发生释放。

我的问题是,有没有一种方法可以在Ptr中的赋值操作期间调用析构函数,或者我需要使用Ptr_count中注释掉的代码来获得正确的内存释放?

很明显,当我退出程序时,析构函数会被调用,内存会以某种方式被释放,但当程序运行时,我认为在这种情况下,引用指针即使在达到零之后也可以继续递减,内存仍然存在。

class Ptr_count {
public:
    Ptr_count() : ptrcnt(new size_t(1)) { }
    ~Ptr_count()
    {
        if(ptrcnt && *ptrcnt <= 0)
            delete ptrcnt;
    }
    size_t operator++() const
    {
        ++(*ptrcnt);
        return *ptrcnt;
    }
    size_t operator--() const
    {
        --(*ptrcnt);
        /*
        if(*ptrcnt == 0) {
            delete ptrcnt;
            return 0;
        }
        */
        if(ptrcnt)
            return *ptrcnt;
        else
            return 0;
    }
    operator bool() const
    {
        return ptrcnt;
    }
    size_t operator*() const
    {
        return *ptrcnt;
    }
private:
    size_t* ptrcnt;
};

template <class T> class Ptr {
public:
    Ptr() : p(0) {}
    Ptr(T* t) : p(t) {}
    Ptr(const Ptr& h) : p(h.p), refptr(h.refptr) { ++refptr; }
    Ptr& operator=(const Ptr& rhs)
    {
        ++(rhs.refptr);
        if(--refptr == 0) {
            delete p;
        }
        refptr = rhs.refptr;
        p = rhs.p;
        return *this;
    }
    ~Ptr()
    {
        if(--refptr == 0) {
            delete p;
        }
    }
    operator bool() const { return p; }
private:
    T* p;
    Ptr_count refptr;
};

编辑::

或者,如果类Ptr_count有自己的赋值运算符,这会解决问题吗?如果我将下面的代码添加到Ptr_count,那么在分配期间引用计数达到0时,我似乎可以释放内存。

void operator=(const Ptr_count& rhs)
{
    if(ptrcnt == 0)
        delete ptrcnt;
    ptrcnt = rhs.ptrcnt;
}

首先,如果这是为了自学,请继续。否则,请停止您正在做的工作,开始使用std::shared_ptr/std::unique_ptr/std::weak_ptr,或者如果您不能使用C++11 std::auto_ptr

现在转到您的代码:

1)在复制构造函数Ptr_count而不是Ptr的复制构造函数中增加引用计数会更安全、更自然,因为Ptr_count类的目的是管理引用计数

这样做之后,您可以完全删除Ptr的复制构造函数

2)Ptr:的赋值运算符中存在不必要的检查

// Counter *must* be greater than 0 here, else p is 0 anyways.
Ptr& Ptr::operator=(const Ptr& rhs)
{
    ++(rhs.refptr);      // Increment your counter to 2 or above.
    if(--refptr == 0) {  // Decrement your counter to 1 or above.
        delete p;        // Never get here.
    }
    refptr = rhs.refptr;
    p = rhs.p;
    return *this;
}

3)您最大的问题是覆盖赋值运算符中的refptrp

Ptr& operator=(Ptr const& rhs)
{
    Ptr temp(rhs);
    std::swap(refptr, temp.refptr);
    std::swap(p, temp.p);
    return *this;
}

应该解决这个问题。

4)您的Ptr_count递减运算符有点坏了。

size_t Ptr_count::operator--() const
{
    --(*ptrcnt); // Access address stored in ptrcnt.
    if(ptrcnt)   // Test if address is valid.
        return *ptrcnt;
    else
        return 0;
}

如果调用此方法时ptrcnt为0,则会因为--(*ptrcnt)而导致访问冲突。无论如何,这应该没有必要,只需删除它:

size_t Ptr_count::operator--() const
{
    return --(*ptrcnt);
}

tl;dr因为代码包含1000多个单词,所以完整的代码:

class Ptr_count {
public:
    Ptr_count() : ptrcnt(new size_t(1)) { }
    Ptr_count(Ptr_count const& rhs) : ptrcnt(rhs.ptrcnt) { ++(*this); }
    ~Ptr_count()
    {
        if(ptrcnt && *ptrcnt <= 0)
            delete ptrcnt;
    }
    size_t operator++()
    {
        return ++(*ptrcnt);
    }
    size_t operator--()
    {
        return --(*ptrcnt);
    }
    operator bool() const
    {
        return ptrcnt;
    }
    size_t operator*() const
    {
        return *ptrcnt;
    }
private:
    size_t* ptrcnt;
};
template <class T> class Ptr {
public:
    Ptr() : p(0) {}
    Ptr(T* t) : p(t) {}
    Ptr& operator=(Ptr const& rhs)
    {
        Ptr temp(rhs);
        std::swap(refptr, temp.refptr);
        std::swap(p, temp.p);
        return *this;
    }
    ~Ptr()
    {
        if(--refptr == 0)
            delete p;
    }
    operator bool() const { return p; }
private:
    T* p;
    Ptr_count refptr;
};

我认为你的问题主要归结为:这个赋值调用了计数器的析构函数吗?

refptr = rhs.refptr;

答案是否定的。就我个人而言,我倾向于实际上而不是将计数器代码封装到一个单独的类中,而是直接在Ptr类中完成。此外,我认为我实现赋值运算符的规范方法将处理正确的行为:

Ptr& Ptr::operator(Ptr other) {
    this->swap(other);
    return *this;
}
void Ptr::swap(Ptr& other) {
    std::swap(this->p, other.p);
    this->ptrcnt.swap(other.ptrcnt);
}
void Ptr_count::swap(Ptr_count& other) {
    std::swap(this->ptrcnt, other.ptrcnt);
}

也就是说,尽管引用计数指针的简单实现是一个有趣的面试问题,但我强烈建议永远不要真正实现引用计数指针[除非你碰巧也实现了标准C++库的其余部分],只使用std::shared_ptr<T>:除了已经制定出如何管理计数的细节之外,这个类实现了一些非常酷的功能,这些功能远远超出了简单的引用计数指针,而且这些功能中的许多在实际代码中都是需要的。