C ++:为什么在这里调用析构函数?

时间:2010-07-09 16:54:21

标签: c++ destructor

我想我不完全理解析构函数在C ++中是如何工作的。以下是我为重新创建问题而编写的示例程序:

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

struct Odp
{
    int id;

    Odp(int id)
    {
        this->id = id;
    }

    ~Odp()
    {
        cout << "Destructing Odp " << id << endl;
    }
};

typedef vector<shared_ptr<Odp>> OdpVec;

bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec)
{
    shpoutOdp.reset();

    for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++)
    {
        Odp& odp = *(iter->get());
        if (odp.id == id)
        {
            shpoutOdp.reset(iter->get());
            return true;
        }
    }

    return false;
}

int main()
{
    OdpVec vec;

    vec.push_back(shared_ptr<Odp>(new Odp(0)));
    vec.push_back(shared_ptr<Odp>(new Odp(1)));
    vec.push_back(shared_ptr<Odp>(new Odp(2)));

    shared_ptr<Odp> shOdp;
    bool found = findOdpWithID(0, shOdp, vec);
    found = findOdpWithID(1, shOdp, vec);
}

main()结束之前,该程序的输出是:

Destructing Odp 0
Destructing Odp 1

为什么会这样?我保留对向量中每个Odp实例的引用。是否与通过引用传递shared_ptr有关?

更新我认为shared_ptr::reset根据MSDN减少参考次数:

  

运营商都减少了   资源的引用计数   目前由* this

拥有

但也许我误解了它?

更新2 :看起来这个版本的findOdpWithID()不会导致析构函数被调用:

bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec)
{
    for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++)
    {
        Odp& odp = *(iter->get());
        if (odp.id == id)
        {
            shpoutOdp = *iter;
            return true;
        }
    }

    return false;
}

4 个答案:

答案 0 :(得分:12)

这一行可能就是让你失望的原因。

shpoutOdp.reset(iter->get());

你在这里做的是从智能指针获取(通过get())裸指针,该指针上没有任何参考跟踪信息,然后告诉shpoutOdp将自身重置为指着裸指针。当shpoutOdp被破坏时,它不知道还有另一个shared_ptr指向同一个东西,shpoutOdp继续销毁它所指向的东西。

你应该做的

shpoutOdp = *iter;

将正确保持引用计数。顺便说一句,reset()确实递减了引用计数器(并且只有在计数达到0时才会销毁)。

答案 1 :(得分:4)

这么多东西几乎都被正确使用了:

bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec)

此处参数shpoutOdp是输入参数的副本。考虑到它是一个共享指针,这并不是一件大事,但这可能不是你想要的。您可能希望通过引用传递,否则为什么首先将它传递给函数。

shpoutOdp.reset();

传入参数时重置参数。
这是否意味着它可能是脏的(那么为什么将它作为输入参数)如果你想传递一些东西,它会使函数返回一个共享指针。

Odp& odp = *(iter->get());

除非你真的需要(并且你真的需要),否则不要使用get on shared指针。提取指针对于获取指针所指向的内容并不是必需的,因为您正在处理指针,因此更容易出错。等效的安全(r)行是:

Odp& odp = *(*iter); // The first * gets a reference to the shared pointer.
                     // The second star gets a reference to what the shared 
                     //pointer is pointing at

这就是出错的地方:

shpoutOdp.reset(iter->get());

您正在从指针创建新的共享指针。不幸的是,指针已经被另一个共享指针管理了。所以现在你有两个共享指针认为它们拥有指针,并且当它们超出范围时将删除它(第一个指针在函数末尾超出范围,因为它是输入参数的副本(而不是比参考))。正确的做法是做一个任务。然后共享指针知道它们正在共享一个指针:

shpoutOdp = *iter; // * converts the iterator into a shared pointer reference

下一行虽然并非完全错误,但假设使用的迭代器是随机访问(对于vector而言是真的)。​​

for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++)

但是这会使代码更加脆弱,因为typedef中的简单更改OdpVec会在没有任何警告的情况下破坏代码。因此,为了使这与正常的迭代器使用更加一致,在检查end()时使用!=并且更喜欢预增量运算符:

for (OdpVec::iterator iter = vec.begin(); iter != vec.end(); ++iter)

答案 2 :(得分:2)

shared_ptr::reset销毁shared_ptr中已有的内容。如果您只想影响那个单独的shared_ptr引用,只需分配给它。

编辑:在回复评论时,您可以通过将for循环的主体更改为:

来修复它
if ((*iter)->id == id)
{
    shpoutOdp = *iter;
    return true;
}

EDIT2:那都说了,你为什么不在这里使用std :: find_if?

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm> //for std::find_if
#include <functional> //for std::bind

struct Odp
{
    int id;

    int GetId()
    {
        return id;
    }

    Odp(int id)
    {
        this->id = id;
    }

    ~Odp()
    {
        std::cout << "Destructing Odp " << id << std::endl;
    }
};

typedef std::vector<shared_ptr<Odp> > OdpVec;

int main()
{
    OdpVec vec;

    vec.push_back(std::shared_ptr<Odp>(new Odp(0)));
    vec.push_back(std::shared_ptr<Odp>(new Odp(1)));
    vec.push_back(std::shared_ptr<Odp>(new Odp(2)));

    OdpVec::iterator foundOdp = std::find_if(vec.begin(), vec.end(), 
        std::bind(std::equal_to<int>(), 0, std::bind(&Odp::GetId,_1)));
    bool found = foundOdp != vec.end();
}

答案 3 :(得分:1)

关于shared_ptr的好处是它在内部处理引用计数。您无需手动递增或递减永远。 (这就是shared_ptr允许你这样做的原因)

当您致电reset时,它只会将当前shared_ptr设置为指向另一个对象(或null)。这意味着现在对reset之前指向的对象的引用少了一个,所以从这个意义上说,ref计数器已经递减了。但这不是你应该调用递减参考计数器的功能。

你永远不需要这样做。只需让shared_ptr超出范围,它就会减少引用计数。

这是RAII的一个例子。

您需要管理的资源(在这种情况下,shared_ptr指向的对象)绑定到堆栈分配的对象(shared_ptr本身),以便自动管理其生命周期。 shared_ptr的析构函数可确保在适当的时候释放指向的对象。