簡體   English   中英

C ++:為什么在這里調用析構函數?

[英]C++: Why is the destructor being called here?

我想我不太了解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有關系嗎?

更新我認為基於MSDNshared_ptr::reset減少了引用計數:

運營商都會減少* 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;
}

這條線可能是您的絆腳石。

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

您在這里所做的是(通過get() )從智能指針get()裸指針,該指針上沒有任何參考跟蹤信息,然后告訴shpoutOdp重置自身以指向裸指針。 shpoutOdp被破壞時,它不知道還有另一個shared_ptr指向同一事物,並且shpoutOdp繼續銷毀它指向的事物。

你應該做

shpoutOdp = *iter;

這樣可以正確維護參考計數。 順便說一句, reset()確實遞減了參考計數器(並且僅在計數達到0時銷毀)。

幾乎正確使用了許多東西:

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

這里的參數shpoutOdp是輸入參數的副本。 考慮到它是一個共享指針,這沒什么大不了的,但這可能不是您想要的。 您可能想通過引用傳遞,否則為什么要首先將其傳遞給函數。

shpoutOdp.reset();

在傳遞參數時對其進行重置。
這是否意味着它可能很臟(然后將其用作輸入參數),如果您要傳遞某些內容,它會使該函數返回一個共享指針。

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

除非確實需要(並且也確實需要),否則不要在共享指針上使用get。 不需要提取指針就可以了解指針所指向的位置,因為您正在處理指針,因此更容易出錯。 等效的safe(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)

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();
}

關於shared_ptr是它在內部處理引用計數。 你並不需要手動遞增或遞減它。 (這就是為什么shared_ptr不允許您這樣做的原因)

調用reset ,它只是將當前的shared_ptr為指向另一個對象(或為null)。 這意味着現在在reset之前對其指向的對象的引用減少了,因此從某種意義上說,ref計數器已減少。 但這不是調用遞減ref計數器的函數。

您根本不需要這樣做。 只要讓shared_ptr超出范圍,它就可以減少引用計數。

這是RAII發揮作用的一個例子。

您需要管理的資源(在本例中為shared_ptr指向的對象)已綁定到堆棧分配的對象( shared_ptr本身),因此可以自動管理其壽命。 shared_ptr的析構函數可確保在適當的時候釋放所指向的對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM