代码之家  ›  专栏  ›  技术社区  ›  Nick Heiner

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

  •  1
  • Nick Heiner  · 技术社区  · 14 年前

    我想我不完全理解析构函数是如何在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 :

    运算符都将 资源的引用计数 目前归*所有

    但也许我误解了?

    更新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 回复  |  直到 14 年前
        1
  •  12
  •   richardwb    14 年前

    这里的这条线可能正是让你困惑的地方。

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

    你在这里所做的就是 get() )智能指针的裸指针,上面没有任何引用跟踪信息,然后告诉 shpoutOdp 将自身重置为指向裸指针。什么时候? 什普托普 被摧毁,它不知道还有另一个 shared_ptr 指的是同一件事, 什普托普 继续摧毁它指向的东西。

    你应该这么做

    shpoutOdp = *iter;
    

    这将正确地保持引用计数。顺便说一下, reset() 减少引用计数器(并且仅在计数为0时销毁)。

        2
  •  4
  •   Loki Astari    14 年前

    几乎正确使用的很多东西:

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

    这里参数shpoutodp是输入参数的副本。考虑到这是一个共享指针,没什么大不了的,但这可能不是你想要的。您可能希望通过引用传递,否则首先为什么要将其传递给函数。

    shpoutOdp.reset();
    

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

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

    除非你真的需要,否则不要使用get-on共享指针(如果你真的需要的话)。提取指针对于获取指针指向的位置并使您更容易出错是不必要的,因为您正在处理指针。等效安全(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
    

    下一行虽然不是完全错误,但它假定所使用的迭代器是随机访问(对于向量来说是正确的)。

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

    但这使得代码更脆弱,因为typedef odpvec中的一个简单更改将在没有任何警告的情况下破坏代码。因此,为了使其更符合正常的迭代器用法,请使用!=对end()进行检查时,也首选预增量运算符:

    for (OdpVec::iterator iter = vec.begin(); iter != vec.end(); ++iter)
    
        3
  •  2
  •   Billy ONeal IS4    14 年前

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

    编辑:响应注释,您可以将for循环的主体更改为:

    if ((*iter)->id == id)
    {
        shpoutOdp = *iter;
        return true;
    }
    

    伊迪丝2:都说了,你为什么不使用std::find_如果在这里?

    #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();
    }
    
        4
  •  1
  •   Stack Overflow is garbage    14 年前

    关于 shared_ptr 它在内部处理引用计数。您不需要手动增加或减少它 曾经 . (这就是为什么 SelddPPTR 允许 你也要这样做)

    当你打电话 reset 只需设置电流 SelddPPTR 指向另一个对象(或空)。这意味着现在对它指向的对象的引用比 重置 因此,从这个意义上说,ref计数器已经递减。但它不是一个函数,您应该调用它来减少引用计数器。

    你不需要这样做。就让 SelddPPTR 超出范围,它负责减少引用计数。

    这是一个例子 RAII 在行动中。

    您需要管理的资源(在本例中是 SelddPPTR )绑定到堆栈分配的对象 SelddPPTR 使其生命周期自动管理。这个 SelddPPTR 的析构函数确保在适当时释放指向对象。