Чтобы использовать shared_ptr, это безопасно? - PullRequest
6 голосов
/ 16 ноября 2010

Я запутался в shared_ptr.

Скажем, у меня есть классы:

class foo {
     int _f;
};
typedef std::shared_ptr<foo> fooptr;

class bar {
    int _b;
};
typedef std::shared_ptr<bar> barptr;

class foobar : public foo, public bar {
    int _fb;
};

int main () {

    foobar *fb1 = new foobar();
    foobar *fb2 = new foobar();

    fooptr f((foo *)fb1);
    barptr b((bar *)fb2);

    return 0;
}

Потому что b.get ()! = Fb2, поэтому он должен аварийно завершить работу при выходе из программы?Или это безопасно?

Ответы [ 4 ]

10 голосов
/ 16 ноября 2010

A shared_ptr<base> может безопасно стать владельцем derived*, даже если base не имеет виртуального деструктора.

Однако, это работает, только если shared_ptr знает, какой тип объекта является наиболее производным, когда он становится владельцем объекта. Если бы вы удалили слепки

fooptr f(fb1);
fooptr b(fb2);

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

foo* f = new foobar();
delete f;

Лучше всего следовать правилу «деструктор базового класса должен быть либо общедоступным, либо виртуальным, либо защищенным и не виртуальным.»

7 голосов
/ 16 ноября 2010

Нет, это не безопасно. foo и bar нужны виртуальные деструкторы, иначе не определено, что произойдет, когда деструктор shared_ptr удалит указатель, который он держит. Конечно, говорить, что это небезопасно, - это не то же самое, что говорить, что это может привести к сбою.

Кроме того, в shared_ptr встроена некоторая магия [*], которая означает, что если вы не преобразуете в foo*, ваш код становится безопасным:

fooptr f(fb1);
barptr b(fb2);

Либо с помощью виртуального деструктора, либо, если вы удалите приведение, когда shared_ptr придет, чтобы удалить указатель, компилятор будет "знать", как вернуть указатель в исходный тип, чтобы вызвать правильные деструкторы и освободить память.

Магия работает только потому, что fb1 имеет тип foobar*. Без виртуального деструктора следующее по-прежнему небезопасно:

foo *fb = new foobar();
fooptr f(fb);

Если вы используете shared_ptr, как это, то нет риска сделать это:

fooptr f(new foobar());

Вы также можете избежать проблемы, заключающейся в том, что в вашем коде, если при втором вызове new foobar() возникает исключение, первый объект пропускается. Если вы собираетесь использовать shared_ptr для управления памятью, вам нужно как можно быстрее получить управление памятью:

fooptr f(new foobar());
barptr b(new foobar());

Теперь, если выбрасывается вторая строка, f будет должным образом разрушен и удалит объект.

[*] "magic" = шаблон конструктора, который хранит в shared_ptr указатель на функцию, которая преобразует сохраненный указатель обратно в правильный тип и затем удаляет его.

0 голосов
/ 16 ноября 2010

Это небезопасно, так как вы используете приведение C-Style в коде C ++.

НЕ используйте приведение в стиле C, используйте такие броски, как static_cast, dynamic_cast, const_cast или reinterpret_cast. Плюс, никакие аварии не означают безопасность.

На самом деле, просто удалите слепки в этом случае.

0 голосов
/ 16 ноября 2010

foo и bar не являются полиморфными классами, и поэтому этот код, скорее всего, приведет к неправильному освобождению указателя.

...