Я в курсе, что такое виртуальный базовый класс, но в данном случае — ТС наследует только от одного класса,
всё остальное — интерфейсы (абстрактные базовые классы). Множественного наследования реализаций — здесь нет.
Так что данная ссылка — не в тему.
P.S. Что касается виртуальных деструкторов, то в первоначальном примере только один класс ConcreteClass осуществляет очистку ресурсов.
Посему, от его позиции в списке объявляемых базовых классов зависимости нет.
Здравствуйте, AlexGin, Вы писали:
AG>Я в курсе, что такое виртуальный базовый класс, но в данном случае — ТС наследует только от одного класса, AG>всё остальное — интерфейсы (абстрактные базовые классы). Множественного наследования реализаций — здесь нет. AG>Так что данная ссылка — не в тему.
Совершенно чётко ясно, что порядок наследования определяет порядок инициализации полей класса и вызова конструкторов/деструкторов. Ваше утверждение что это не так — неверно.
В си++ пока ещё нет понятия интерфейса. Это может быть договоренность в рамках конкретного проекта.
Здравствуйте, SaZ, Вы писали:
SaZ>Здравствуйте, AlexGin, Вы писали:
AG>>Я в курсе, что такое виртуальный базовый класс, но в данном случае — ТС наследует только от одного класса, AG>>всё остальное — интерфейсы (абстрактные базовые классы). Множественного наследования реализаций — здесь нет. AG>>Так что данная ссылка — не в тему.
SaZ>Совершенно чётко ясно, что порядок наследования определяет порядок инициализации полей класса и вызова конструкторов/деструкторов. Ваше утверждение что это не так — неверно.
У абстрактных базовых классов, выполняющих роль интерфейсов — полей нет (также как и конструкторов).
Насчёт деструкторов — да, вероятно, есть пустые вирт-деструкторы (а правильнее — чисто виртуальные деструкторы),
то-есть конкретно — отработает только тот, что в ConcreteClass.
Посему, данное твоё высказывание, правильное в общем случае, здесь не актуально.
SaZ>В си++ пока ещё нет понятия интерфейса.
Это понятно, но в данном случае — роль интерфейса выполняет именно он.
Вопрос был — о конкретном куске кода, не надо уходить от вопроса!
SaZ>Это может быть договоренность в рамках конкретного проекта.
P.S. Здесь ставился именно конкретный вопрос, на который я дал верный ответ.
Дальнейшие теоретизирования с твоей стороны, только дополнительно показывают отсутствие практических возражений.
Здравствуйте, Maniacal, Вы писали:
M>A* в B* не конвертируется, естественно, это C* конвертируется в A* и в B*, что и нужно проверить
C может быть вообще в коде плагина какого-нибудь, который пока ещё не написан и не спроектирован даже. И будет поставлен сильно позже чем хост этих плагинов
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Maniacal, Вы писали:
M>Ну или для всех интерфейсов сделать базовый интерфейс с функциями isSerializable и пр, в унаследованных реализовать возврат true/false
Лучше уж тогда getISerializable, который вернёт интерфейс или 0
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Максим Рогожин, Вы писали:
МР>Привет!
МР>Наследование только от интерфейсов: МР>
МР>class SomeClass : public Interface1, // интерфейс
МР> public Interface2, // интерфейс
МР> ...
МР> public InterfaceN // интерфейс
МР>{
МР>public:
МР> ~SomeClass();
МР>//...
МР>};
МР>
МР>~SomeClass() переопределяет сразу все деструкторы интерфейсов?
У класса может быть только один деструктор!
Деструктор — не принимает никаких аргументов.
В деструкторе следует очистить все ресурсы, используемые в объекте данного класса и требующие очистки.
МР>Для удаления объекта SomeClass можно любой интерфейс использовать? Например, так можно?
МР>
МР>std::unique_ptr<Interface2> obj(new SomeClass()); // удаляться будет через интерфейс Interface2
МР>
Да, если в Interface2 определён виртуальный деструктор.
Если же там вирт/деструктора нет, то очистки ресурсов не будет
Здравствуйте, AlexGin, Вы писали:
AG>У класса может быть только один деструктор!
Ага, конечно. Смотрите, у класса A сразу три деструктора!
class A : public B, public C {};
AG>Деструктор — не принимает никаких аргументов. AG>В деструкторе следует очистить все ресурсы, используемые в объекте данного класса и требующие очистки.
Главное — не выкинуть при этом исключений
И вообще, это зависит от реализуемой логики.
Здравствуйте, SaZ, Вы писали:
SaZ>Здравствуйте, AlexGin, Вы писали:
AG>>У класса может быть только один деструктор!
SaZ>Ага, конечно. Смотрите, у класса A сразу три деструктора! SaZ>
SaZ>class A : public B, public C {};
SaZ>
У класса A — один деструктор! При разрушении объекта класса A — вызываются деструкторы базовых классов (если они определены) — точнее: сначала свой деструктор (класса A), затем деструкторы базовых классов. Это поведение — стандартно для C++.
При создании объекта — всё происходит противоположно:
Первоначально отработают конструкторы базовыйх классов, а затем уже конструктор нашего (наследника).
AG>>Деструктор — не принимает никаких аргументов. AG>>В деструкторе следует очистить все ресурсы, используемые в объекте данного класса и требующие очистки.
SaZ>Главное — не выкинуть при этом исключений
+100500
Также, как и в любых деструкторах...
SaZ>И вообще, это зависит от реализуемой логики.
+100500
...естесственно...
Здравствуйте, AlexGin, Вы писали:
AG>У класса может быть только один деструктор!
Спасибо! А вот так можно делать?
class Interface1 {
public:
virtual ~Interface1() {} // vtbl[0]virtual void doSomething() = 0; // vtbl[1] Такой же как и в Interface2, но на первой позиции в vtblvirtual void doSomething1() = 0; // vtbl[2]
};
class Interface2 {
public:
virtual ~Interface2() {} // vtbl[0]virtual void doSomething2() = 0; // vtbl[1]virtual void doSomething() = 0; // vtbl[2] Такой же как и в Interface1, но на второй позиции в vtbl
};
class SomeClass : public Interface1,
public Interface2
{
public:
~SomeClass(); // vtbl[0]void doSomething1() override; // vtbl[1]void doSomething2() override; // vtbl[2]void doSomething() override; // vtbl[3]
//...
};
std::unique_ptr<Interface1> obj1(new SomeClass());
std::unique_ptr<Interface2> obj2(new SomeClass());
obj1->doSomething();
obj2->doSomething();
Тут все то же самое что и с деструкторами? Метод doSomething() можно вызывать через любой интерфейс? Не зависимо от того на каком месте он в интерфейсах объявлен?
Здравствуйте, Максим Рогожин, Вы писали:
МР>Здравствуйте, AlexGin, Вы писали:
AG>>У класса может быть только один деструктор!
МР>Спасибо! А вот так можно делать?
...
ИМХО — неудачный дизайн архитектуры проекта, я бы не советовал так делать
Например: у нас есть сервер БД и специализированный сервер, выполняющий некую обработку наших запросов.
И там и там есть метод, выполняющий подсоединение: Connect()
Для меня НЕ ясно — к чему подсоединяется объект типа твоего SomeClass — к серверу Базы Данных, или к спец-серверу?
В общем:
Компилятор — тебя поймёт, но коллеги по проекту (а возможно — ты же сам, через полгода-год) будут плеваться...
... МР>Тут все то же самое что и с деструкторами? Метод doSomething() можно вызывать через любой интерфейс? Не зависимо от того на каком месте он в интерфейсах объявлен?
Ещё раз подчеркну: неоднозначности для компилятора — не будет, компилятор такой код успешно проглотит.
Так, если оба интерфейса имеют одноименный мембер, то — зачем наследовать от обоих?
Может быть — удачнее было бы отнаследоать новый интерфейс (назовем его Interface1Cool):
class Interface1 {
public:
virtual ~Interface1() {} // vtbl[0]virtual void doSomething() = 0; // vtbl[1] Такой же как и в Interface2, но на первой позиции в vtblvirtual void doSomething1() = 0; // vtbl[2]
};
class Interface1Cool : public Interface1
{
virtual void doSomethingCool() = 0;
virtual void doSomething2() = 0;
};
class SomeClass : public Interface1Cool
{
public:
~SomeClass() {};
void doSomething1() override;
void doSomething2() override;
void doSomething() override;
void doSomethingCool() override;
//...
};
P.S. В данном вопросе — множественного наследования интерфейсов вполне можно избежать,
что только поспособствует повышению читабельности кода и упрощению работы с твоим проектом
Здравствуйте, AlexGin, Вы писали:
AG>Ещё раз подчеркну: неоднозначности для компилятора — не будет, компилятор такой код успешно проглотит.
А как компилятор будет определять где в vtbl какой метод находится? Вот, например:
class Interface1 {
public:
virtual ~Interface1() {} // vtbl[0]virtual void doSomething() = 0; // vtbl[1] virtual void doSomething1() = 0; // vtbl[2] = void doSomething1()
};
class SomeClass : public Interface1,
public Interface2
{
public:
~SomeClass(); // vtbl[0]void doSomething1() override; // vtbl[1] = void doSomething1()void doSomething2() override; // vtbl[2]void doSomething() override; // vtbl[3]
//...
};
std::unique_ptr<Interface1> obj1(new SomeClass());
obj1->doSomething1(); // как компилятор определяет где в vtbl находится метод doSomething1()?
// метод doSomething1() в Interface1 - это vtbl[2]
// но в классе SomeClass метод doSomething1() - это уже vtbl[1]
Как компилятор определяет какой код сгенерировать?
call vtbl[2]; // такой?
call vtbl[1]; // или такой?
Здравствуйте, уважаемый Максим Рогожин, Вы писали:
... МР>А как компилятор будет определять где в vtbl какой метод находится?
Современный компилятор C++ это достаточно догадливая система
Работая с такой системой, абстрагируешься от решений, принятых в ней.
Концентрируеш внимание на своей (точнее — пользовательской) задаче.
Вы, уважаемый Максим Рогожин, собралися разработать свой компилятор C++
Так как лично мне не требовалось и не приходилось заниматься разработкой компилятров, то не буду вводить Вас в заблуждение беспочвенными догадками.
P.S. Абстрагирование от ненуждых деталей — очень важное свойство для разработчика.
Так, например, меня не интересует какой именно компилятор у тебя/твоей команды.
Для какой OS вы пишете свой проект.
Мы знаем, что имеется таблица виртуальных функций у класса, у которого есть хотя бы одна виртуальная функция (или вирт-деструктор).
От подробностей реализации данного механизма — предлагаю абстрагироваться.
По крайней мере до тех пор, пока нам не требуется делать свой компилятор
Здравствуйте, Максим Рогожин, Вы писали:
МР>Как компилятор определяет какой код сгенерировать?
Как уже говорили — этот аспект не регулируется стандартом, каждый компилятор делает как ему угодно. Далее один из возможных вариантов:
Для SomeClass таблица будет выглядеть не совсем так как ты ее нарисовал. Примерно так (упрощенно):
//первая часть - собственная
vtbl[0] -> SomeClass::~SomeClass
vtbl[1] -> void SomeClass::doSomething1()
vtbl[2] -> void SomeClass::doSomething2()
vtbl[3] -> void SomeClass::doSomething()
//вторая часть - для базы Interface1
vtbl[4] -> SomeClass::~SomeClass
vtbl[5] -> void SomeClass::doSomething()
vtbl[6] -> void SomeClass::doSomething1()
//еще часть - для базы Interface2
vtbl[7] -> SomeClass::~SomeClass
vtbl[8] -> void SomeClass::doSomething2()
Так, например, при касте SomeClass->Interface1 — в качестве таблицы для Interface1 используется вторая часть таблицы SomeClass
при касте SomeClass->Interface2 — третья часть
В такой схеме неоднозначностей при выборе слота метода — нет.
на этом полигоне можно погонять вживую https://godbolt.org/g/ZVZzmy
в частности, увидеть реальные таблицы, строки 357, 351, 345, 327 в сгенерированном ассемблере