C ++: Почему деструктор вызывается здесь? - PullRequest
1 голос
/ 09 июля 2010

Полагаю, я не до конца понимаю, как работают деструкторы в 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 ]

12 голосов
/ 09 июля 2010

Эта строка прямо здесь, вероятно, то, что сбивает вас с толку.

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

То, что вы делаете здесь, - это получение (через get()) обнаженного указателя от интеллектуального указателя, который не будет иметь никакой информации об отслеживании ссылок, а затем shpoutOdp, чтобы сбросить себя, чтобы он указывал на обнаженный указатель. Когда shpoutOdp разрушается, он не осознает, что есть другой shared_ptr, который указывает на то же самое, и shpoutOdp продолжает уничтожать то, на что он указывает.

Вы должны просто сделать

shpoutOdp = *iter;

, который будет правильно поддерживать счетчик ссылок. Кроме того, reset() уменьшает счетчик ссылок (и уничтожает только, если счет достигает 0).

4 голосов
/ 09 июля 2010

Так много вещей, которые используются почти правильно:

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

Здесь параметр shpoutOdp является копией входного параметра. Не так уж важно, учитывая, что это общий указатель, но, вероятно, это не то, что вы намеревались. Вы, вероятно, хотели бы передать по ссылке, в противном случае зачем передавать его функции.

shpoutOdp.reset();

Сброс параметра при его передаче.
Означает ли это, что он может быть грязным (тогда зачем иметь его в качестве входного параметра), он заставляет функцию возвращать общий указатель в результате, если вы хотите что-то передать.

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

Не используйте get для общих указателей, если только вам это не нужно (и вам это действительно нужно). Извлечение указателя не является необходимым для получения того, на что указывает указатель, и повышает вероятность ошибок, потому что вы работаете с указателями. Эквивалентная безопасная (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)
2 голосов
/ 09 июля 2010

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();
}
1 голос
/ 09 июля 2010

Приятной особенностью shared_ptr является то, что он обрабатывает подсчет ссылок внутри.Вам не нужно вручную увеличивать или уменьшать когда-либо .(И именно поэтому shared_ptr не также позволяет вам делать это)

Когда вы вызываете reset, он просто устанавливает текущий shared_ptr, чтобы он указывал на другой объект(или ноль).Это означает, что теперь существует на одну ссылку меньше на объект, на который он указывал до reset, поэтому в этом смысле счетчик ссылок был уменьшен.Но это не функция, которую вы должны вызывать для уменьшения счетчика ссылок.

Вам никогда не нужно этого делать.Просто позвольте shared_ptr выйти из области видимости, и он позаботится об уменьшении числа ссылок.

Это пример RAII в действии.

Ресурс, который вынужно управлять (в этом случае объект, на который указывает shared_ptr), привязан к объекту, выделенному в стеке (сам shared_ptr), так что его время жизни управляется автоматически.Деструктор shared_ptr обеспечивает освобождение намеченного объекта при необходимости.

...