У меня есть еще один вопрос о множественном наследовании, у которого есть ответ, например: здесь (но сфокусирован на отпечатке) или здесь (слишком расплывчато), но большинство ответов, на которые я наткнулся, этоподчеркивая недостатки производительности.Однако (как утверждает Бьярне Страуструп здесь ), это языковая особенность, которая должна быть предпочтительнее обходных путей.Вот более длинный пример, чтобы проиллюстрировать вопрос, следующий за примером:
Пример
В Чешской Республике номер рождения (эквивалентный SSN) назначается в следующем формате: YYMMDDXXX, поэтому давайтекласс для получения даты рождения в стандартном DMYYYY:
class Human {
protected:
char output[11];
char input[10];
public:
Human (const char* number) {
strncpy(input, number, 10);
if(!number[10]) throw E_INVALID_NUMBER;
}
static int twoCharsToNum(const char* str) {
if(!isdigit(str[0]) || !isdigit(str[1])) throw E_INVALID_NUMBER;
return (str[0]-'0')*10 +str[1]-'0';
}
const char* getDate() {
sprintf(output, "%d.%d.%d", getDay(), getMonth(), getYear());
return output;
}
// range check omitted here to make code short
virtual int getDay() { return twoCharsToNum(input+4); }
virtual int getMonth() { return twoCharsToNum(input+2); }
virtual int getYear() { return twoCharsToNum(input)+1900; }
};
Три метода являются виртуальными, потому что женщины получают +50 к своему месяцу.Итак, давайте унаследуем классы «Мужчина» и «Женщина», чтобы правильно получить дату:
class Man : public Human {
public:
using Human::Human;
};
class Woman : public Human {
public:
using Human::Human;
int getMonth() {
int result = twoCharsToNum(input+2)-50;
if(result<0) throw E_INVALID_GENDER;
if(result==0 || result>12) throw E_INVALID_RANGE;
return result;
}
};
С 1954 года число состоит из 4 цифр, а не 3 (за этим стоит печальная история, упомянутая в конце этого вопроса).Если библиотека была написана в 1944 году, через десять лет кто-нибудь может написать Фасад, чтобы правильно определить дату рождения для будущих тысячелетий:
class Human2 : public Human {
public:
using Human::Human;
virtual int getYear() {
int year = twoCharsToNum(input);
if(year<54 && strlen(number)==10) year+= 2000;
else year+= 1900;
return year;
}
};
class Man2 : public Human2 {
public:
using Human2::Human2;
};
В class Woman2
нам нужен метод Woman::getMonth
, поэтому нам нуженЧтобы решить проблему с бриллиантами:
class Human2 : virtual public Human { ... };
class Woman : virtual public Human { ... }; // here is the real issue
class Woman2 : public Human2, public Woman {
using Human2::Human2;
using Woman::Woman;
};
Диаграмма проблемы с бриллиантами:
Woman2
^ ^
| |
Woman Human2
^ ^
| |
Human
Вопрос
Проблема в том, что Human
, Man
и Woman
может быть в виде двоичной библиотеки, где клиентский код не может переписать наследование в виртуальное.Итак, как правильно спроектировать расширяемую библиотеку, чтобы включить множественное наследование?Должен ли я сделать каждое наследование в области действия библиотеки виртуальным (поскольку я заранее не знаю, как его можно расширить), или есть какой-нибудь более элегантный универсальный дизайн?
Что касается производительности: разве это необласть низкоуровневого программирования и оптимизации компилятора, не должна ли перспектива проектирования превалировать над программированием высокого уровня?Почему компиляторы не выполняют автоматическую виртуализацию наследования, как в RVO или inline
решениях о вызовах?
Грустная история, стоящая за примером
В 1954 году некоторые технически вдохновленные бюрократы решили, что десятыйШифр будет добавлен таким образом, что число будет делиться на 11. Позже гений выяснил, что есть числа, которые нельзя изменить таким образом.Поэтому он выдал исключение, что в этих случаях последнее число будет равно нулю.Позднее в том же году была издана внутренняя директива, запрещающая подобные исключения.Но в то же время было выпущено более 1000 номеров рождений, которые не делятся на 11, но все же являются законными.Независимо от этого беспорядка, по числу чисел можно определить век года до 2054 года, когда мы испытаем возрождение 2000 года.Увы, также распространена практика, когда иммигрантам, родившимся до 1964 года, присваивается 10-значный номер рождения.