[C++] деструктор при множественном наследовании

erotic


struct Base
{
virtual ~Base = 0;
};

struct Base2
{
virtual ~Base2 {}
};

struct Target : public Base, public Base2
{
~Target {}
};

int main
{
Target* t = new Target;
delete t;
return 0;
}

make virtual_destructor
g++ virtual_destructor.cpp -o virtual_destructor
/tmp/ccY45UVi.o: In function `Target::~Target':
virtual_destructor.cpp:(.text._ZN6TargetD1Ev[Target::~Target]+0x41): undefined reference to `Base::~Base'
/tmp/ccY45UVi.o: In function `Target::~Target':
virtual_destructor.cpp:(.text._ZN6TargetD0Ev[Target::~Target]+0x41): undefined reference to `Base::~Base'
collect2: выполнение ld завершилось с кодом возврата 1
make: *** [virtual_destructor] Ошибка 1
Это нормально? Судя по всему, деструктор Target хочет вызвать деструкторы обоих базовых классов, но определения одного из них нет.
Если это так, то выходит, что интерфейсные классы с абстрактным деструктором нельзя использовать во множественном наследовании, не создав промежуточный класс с определенным деструктором, или просто не определив где-то вовне деструктор такого абстрактного класса.
А как в других компиляторах и по стандарту?

Serab

Или дописать
Base::~Base {}

Это ничему не повредит и стандарту не противоречит. Как workaround.
Но имхо, это, конечно, не нормально.

Dasar

что такое абстрактный деструктор, и зачем это надо?

Serab

Да, кстати, это тоже workaround, потому что в C++ нету ключевого слова abstract, которое можно было бы навесить на класс. Так что воркэраундить воркэраунд — это нормально, тем более в C++.

Vlad1953

12.4.7 A destructor can be declared virtual (10.3) or pure virtual (10.4); if any objects of that class or any derived class are created in the program, the destructor shall be defined. If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly- declared) is virtual.
То есть код ТС нарушает стандарт, а компилятор, вроде как, все правильно сделал.

Serab

Ты про «shall be defined»? Вроде бы это shall, а не must. Короче что-то я не понял.

Vlad1953

Да, имею ввиду это.
См. rfc2119:
1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that the
definition is an absolute requirement of the specification.

erotic

if any objects of that class or any derived class are created in the program, the destructor shall be defined
А, понятно. Тут даже множественное наследование не при чем. Вот это аналогично не работает:

struct Base
{
virtual ~Base = 0;
};

struct Target : public Base
{
~Target {}
};

int main
{
Target* t = new Target;
delete t;
return 0;
}

По-любому нужно определение.

erotic

что такое абстрактный деструктор, и зачем это надо?
Я подзабыл и думал, что можно объявить аналогично абстрактной функции абстрактный деструктор: virtual ~Base = 0. Оно и можно, только его определение все равно нужно предоставить, оказывается.

Maurog

Я подзабыл
из той же серии:
1) можно создать экземпляр
http://codepad.org/4AYt77Ui
2) нельзя создать экземпляр
http://codepad.org/HXLhyByS и линкуется http://codepad.org/3dqP1uop

Serab

А да, затупил.

Serab

ха-ха-ха, вот это сюрприз! Выходит я такие штуки g++ никогда и не компилил :ooo:

enochka1145

// что такое абстрактный деструктор, и зачем это надо?
Если хочется сделать класс абстрактным, а чисто виртуальных методов нет, можно объявить чисто виртуальным деструктор. Определить его всё равно придётся, естественно.

Dasar

>Определить его всё равно придётся, естественно.
вот эта концепция в меня совсем не укладывается...
как можно абстрактный метод(деструктор) при этом еще и определить... либо шубу снять надо, либо трусы одеть..(C)

Vlad1953

И не надо ее в себя укладывать,
не даром же говорят, что c++ cripples
mind...

Serab

Ну, кстати, я подумал, это и правда естественно (раньше мне так не казалось просто потому что деструкторы в C++ неявно автоматически вызывают унаследованный деструктор, для обычных функций это, естественно не так.
Ну а про чисто виртуальную функцию и ее определение, это надо просто привыкнуть: чистая виртуальность — значит надо явно указать, что хочешь вместо нее, но можно предоставить реализацию по умолчанию. Вроде в некоторых местах так рекомендуют делать (специалисты по шаблонам гыгы)

Vlad1953

Кстати, нашел небесполезную статью по теме.

Serab

прочитал. Еще прочитал ответ (в жежешечке). Мысль про виртуальные открытые функции — очень хорошая, про защищенный деструктор — пурга имхо.

Vlad1953

Ну я не то чтобы ратую сам за protected virtual ~,
но точка зрения такая небезынтересна.

Serab

разжуй для меня, пожалуйста! Вот я тоже еще сперва подумал, что он имеет в виду защищенный виртуальный, но там у него написано защищенный НЕ виртуальный. И вообще, если уж так заморачиваться и публичные методы делать невиртуальными, то защищенные тоже надо бы, protected — это ведь тоже интерфейс, просто на другом уровне.

Vlad1953

Да я эту статью, честно говоря, по диагонали
проглядел. Вообще protected ~, хоть виртуальный,
хоть нет, это трюк для людей, которым
хочется странного.

Serab

Ну да, тут не уйдешь, типа разрешить наследование, но запретить создание на стеке. Ок.

Vlad1953

Обычно, и вправду, когда реализуют на cxx template method,
используют private virtual.
Область применения protected virtual мне сходу не очевидна.

Serab

Дааа, template method — брат полиморфизма!

Vlad1953

Тут ирония, или
мне почудилось?

Serab

Почудилось наверное. Типа мне кажется, что где виртуальные фукнции там обязательно template method в некотором смысле.

Vlad1953

Я тоже так cчитаю, хотя в "несерьезных"
проектах сам частенько нарушаю NVI принцип,
и тогда здесь TM уже и не пахнет :)

Vlad1953

Кстати, вот одно небесполезное
применение: сделать некий базовый
класс с защищенным деструктором и
зафрендить shared_ptr. Потом
заставить всех от него наследоваться.
Так можно принудить подчиненных
кодомакак не использовать delete
где не надо.

slonishka

вы все дураки и не лечитесь.
зафрендить shared_ptr
http://shared-ptr.livejournal.com/611.html

apl13

Firefox не может найти сервер shared_ptr.livejournal.com.

slonishka

fixed :(

Vlad1953

ROTFLMAO

Maurog

сделать некий базовый
класс с защищенным деструктором и
зафрендить shared_ptr.
вы так делали? у вас компилируется код?
что-то мне подсказывает, что это не работает :o

Vlad1953

УМВРЧЯДНТ?

#include <tr1/memory>
#include <iostream>

using std::tr1::shared_ptr;
using std::cerr;

class B {
friend class std::tr1::_Sp_deleter<B>;
protected:
virtual ~B {
cerr<<"~B\n";
}
};

class D1: public B {
protected:
~D1 {
cerr<<"~D1\n";
}
};

class D2: public B {
~D2 {
cerr<<"~D2\n";
}
};

int main {
shared_ptr<B> b(static_cast<B*>(new D1;
return 0;
}

Maurog


friend class std::tr1::_Sp_deleter<B>;

 shared_ptr<B> b(static_cast<B*>(new D1; 

 :grin:

Vlad1953

"Никто не обещал, что будет легко
Я понимаю тебя, Садко,
Но мое чувство юмора рекомендует мне всплыть."
(c) БГ

Serab

Да не, ну косяк же, имхо :grin:

Vlad1953

Блин, да я эту хрень писал
в метро с телефона и на нем
же компиляйнил, че ты хочешь
:grin:
А вообще реальная тема:
1) Работает
2) Кодомакаки все равно не поймут

doublemother

Не, ну tr1 — это не по-людски.

Vlad1953

А что по-людски, cxx0x?

doublemother

На самом деле и cxx0x до принятия не до конца кошерен, хотя кто ж спорит, юзать удобно и хочется.
Но у него есть существенное преимущество перед tr1 — у всего, что лежит в tr1, с очень большой вероятностью постепенно «поломаются» и инклюды, и неймспейс.

Vlad1953

Ну блин, если поломаются —
поправлю пост, уговорил :)

Serab

добавил gcc твоего участка в cron, будь готов.

Vlad1953

Жду ответного гудка.

Serab

О, пришел ответ:
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.4.4/include/g++-v4/tr1/memory:60,
from <stdin>:1:
<stdin>: In constructor ‘std::tr1::__shared_count<_Lp>::__shared_count(_Ptr) [with _Ptr = B*, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.4.4/include/g++-v4/tr1/shared_ptr.h:377: instantiated from ‘std::tr1::__shared_ptr<_Tp, _Lp>::__shared_ptr(_Tp1*) [with _Tp1 = B, _Tp = B, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-pc-linux-gnu/4.4.4/include/g++-v4/tr1/shared_ptr.h:842: instantiated from ‘std::tr1::shared_ptr<_Tp>::shared_ptr(_Tp1*) [with _Tp1 = B, _Tp = B]’
<stdin>:29: instantiated from here
<stdin>:10: error: ‘virtual B::~B’ is protected
/usr/lib/gcc/x86_64-pc-linux-gnu/4.4.4/include/g++-v4/tr1/shared_ptr.h:119: error: within this context

Vlad1953

Ну что я могу тут сказать?..
У меня на телефоне 4.2.2, там
работает. С тех пор поменяли
реализацию shared_ptr.
Щас поправлюю

Vlad1953

О, вот так надо:

#include <tr1/memory>
#include <iostream>

using std::tr1::shared_ptr;
using std::cerr;

class B {

friend class std::tr1::_Sp_deleter<B>;
friend class std::tr1::__shared_count<(__gnu_cxx::_Lock_policy)2u>;

protected:
virtual ~B {
cerr<<"~B\n";
}
};

class D1: public B {
protected:
~D1 {
cerr<<"~D1\n";
}
};

class D2: public B {
~D2 {
cerr<<"~D2\n";
}
};

int main {
shared_ptr<B> b(static_cast<B*>(new D1;
return 0;
}

Maurog

О, вот так надо:
ну вы же понимаете, что это непортабельное решение, потому что вы используете нюансы имплементации
остановитесь :grin:

Vlad1953

Давайте Вашу имплементацию,
портируем :grin:
Оставить комментарий
Имя или ник:
Комментарий: