Использование виртуального наследования
Мне нужно написать соглашение о кодировании, которое будет использоваться как новичками, так и опытными разработчиками на С++. Правило о наследовании для динамического полиморфизма выглядит следующим образом:
- Для динамического полиморфизма рассмотрим использование одиночного наследования (древовидная иерархия), возможно, с множественным наследованием абстрактных интерфейсов
- для наследования по иерархии (базовые классы и т.д.), по умолчанию, используйте public inheritance
- для наследования абстрактного интерфейса, по умолчанию, используйте общедоступное виртуальное наследование
За этим правилом последует подробная информация о реализации, возможных исключениях и т.д.
Итак, вопрос: Является ли это правило желательным как для новичков, так и для опытных разработчиков на С++? (плюсы/минусы, а также источники и ссылки приветствуются)
Я вижу:
Плюсы:
- правило, легко используемое новичками, без ограничения опытных разработчиков.
- знакомы тем, кто уже знаком с интерфейсами Java/.NET
- утилит проблемы, связанные с виртуальным наследованием реализации (поскольку он зарезервирован для абстрактных интерфейсов), а также не виртуальное наследование (возможная двусмысленность при кастинге в класс интерфейса)
Минусы:
- небольшая производительность (скорость при передаче на интерфейс, размер виртуальных таблиц, дополнительные указатели в экземпляре класса)
Примечание. Я прочитал следующие онлайн-источники:
Примечание 2: Использование имени "абстрактного интерфейса" придумано после использования Sutter и Alexandrescu в пункте 36 "Стандарты кодирования С++"
Это один случай, который должен работать (его эквивалент Java/С# с использованием интерфейсов просто работает), но это не в С++, если наследование интерфейса не является виртуальным:
class A
{
public :
virtual ~A() = 0 {}
} ;
class B : public A {} ; // should have been virtual to avoid the error
class C : public A {} ; // should have been virtual to avoid the error
class D : public B, public C
{
public :
virtual ~D() {}
} ;
void foo(A * c) {}
void bar(D * d)
{
foo(d) ; // Error: ambiguous conversions from 'D *' to 'A *
}
И да, явное литье, чтобы удалить двусмысленность, является неправильным решением (явное литье, как правило, является неправильным решением).
Ответы
Ответ 1
Знаешь что? Вы уже даете всю важную информацию в вопросе. Я ничего не вижу, чтобы ответить на техническом уровне. И, судя по всему, никто другой не видел серьезных технических проблем с тем, что вы разместили.
Я отвечу на ваш смелый вопрос: Да, он подходит как для новичков, так и для профессионалов.
- У Newbs есть несколько полезных технических рекомендаций.
- Плюсы могут делать то, что хотят, если они могут дать обоснование, потому что вы квалифицируете свои правила с помощью "рассмотреть" и "по умолчанию", так что в принципе никто другой не может сказать, что вам нужно сделать это или так из-за правил стиля, потому что фраза ваших правил уже допускает исключения.
Ответ 2
Пример вашего наследования не работает, если он не является виртуальным из-за классической проблемы множественного наследования в С++, алмаза смерти. В принципе, если вы не указываете виртуальное наследование, каждый родительский класс (B, C) имеет свои собственные объекты базы A. Это делает доступ к нестационарным функциям и переменным базового класса (преобразование, а также предположение) неоднозначным. Я не могу представить ситуацию, в которой вы могли бы избежать этого в С++.
Изменить: для записи это рабочий код:
class A
{
public :
virtual ~A() {}
};
class B : virtual public A {};
class C : virtual public A {};
class D : virtual public B, virtual public C
{
public :
virtual ~D() {}
};
void foo(A * c) {}
void bar(D * d)
{
foo(d);
}
int main(void)
{
D d;
foo(&d);
return 0;
}
Ответ 3
Ваше первое правило исключает class D
вообще:
Для динамического полиморфизма рассмотрим использование одиночного наследования (древовидная иерархия), возможно с множественным наследованием абстрактных интерфейсов
Ваши правила в порядке.
Итак, каков ваш вопрос? Что осталось сейчас?