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

时间:2013-12-06 00:01:26

标签: c++ memory-management

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

当班级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;
};

EDIT ::

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

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

2 个答案:

答案 0 :(得分:1)

首先,如果这只是为了自学,那就继续吧。否则停止正在进行的操作并开始使用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;博士 因为代码超过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;
};

答案 1 :(得分:1)

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

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>:除了编写关于如何管理计数的细节之外,这个类还实现了一些非常酷的功能,这些功能超出了你的简单引用计数指针和许多实际代码中实际上需要这些功能。