Подтвердить что ты не робот

Вы никогда не хотите использовать виртуальное наследование взаимного базового класса, если вы используете множественное наследование?

Если у вас есть отношения-наследования, реализованные с использованием общего наследования, и у вас есть алмаз наследования, у вас будет что-то вроде:

  • класс потока
  • классы потока ввода и вывода, полученные из потока
  • класс потока ввода/вывода, полученный из обоих

В этом случае, как используется в стандартной библиотеке (?), в той мере, в которой iostream как is-istream, так и is-the ostream, istream isa-stream и ostream - это поток, и, кроме того, они являются одним и тем же потоком, любые функции в потоке, которые имеет смысл применять к iostream, должны иметь дело с одной и той же базовой структурой.

В С++ для того, чтобы копии потока в istream и ostream могли быть разделены, они должны быть фактически унаследованы ими.

Однако, если вы предпочитаете, вы не можете наследовать фактически и каждый раз, когда вы ссылаетесь на члена базового класса, укажите, какая из двух копий (одна в istream или одна в ostream) вы хотите (либо путем литья, либо используя область:: blah).

Мой вопрос в том, что [edit: есть ли какой-нибудь другой случай, где], кроме "Это не действительно отношение is-a, я использовал naughtily используемое публичное наследование как синтаксическое удобство, когда оно не было концептуально действительным" или "Мне никогда не нужно полиморфно ссылаться на базовый класс из самого производного класса, поэтому невероятно небольшие накладные расходы не стоят этого", есть какая-то причина, по которой она была бы концептуально действительной для наследования практически нет и иметь две копии базы класс, по одному для каждого промежуточного класса сестры?

4b9b3361

Ответ 1

Перейдем к простому примеру.

   B           B1   B2
   |             \ /
   D              D

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

Справа оба B1 и B2 являются независимыми основами D, а D можно получить полиморфно, используя B1* или B2* (или ссылки). Надеюсь, мы сможем согласиться с тем, что это также действительный дизайн OO (несмотря на то, что Sun считает это слишком стеснительным сгибанием для Java-программистов; -P). Затем рассмотрите возможность использования некоторых базовых функциональных возможностей как из B1, так и B2 и сделанных повторно: скажем, они оба работают с произвольным списком чисел, к которым они могут разумно предоставить прямой /public доступ:

   Container<int>   Container<int>
              \     /
              B1   B2
                \ /
                 D

Если это еще не щелчок, возможно:

   Container<int>   Container<int>
              \     /
            Ages  Heights
                \ /
             Population

Разумно сказать, что Ages is-a Container<int>, хотя он может добавить некоторые удобные функции, такие как average, min, max, num_teenagers. То же самое для Heights, возможно, с другим набором функций удобства. Ясно, что a Population можно разумно заменить на коллекцию Ages или Heights (например, size_t num_adults(Ages&); if (num_adults(my_population)) ...).

Дело здесь в том, что каждый поддерживающий контейнер не должен иметь отношения 1:1 с другими производными классами, такими как Population; скорее, он должен быть ровно 1:1 с его производным классом.

Опять же, используется ли композиция или наследование - решение для дизайна интерфейса, но это не обязательно недействительно, чтобы публиковать эти контейнеры таким образом. (Если речь идет о сохранении инвариантов Population, таких как Ages::empty() == Heights::empty(), мутационные функции данных Container<int> могут быть сделаны protected, тогда как const функции-члены были public.)

Как вы заметили, Population не имеет однозначного отношения is-a с Container<int>, и код может нуждаться в явной неоднозначности. Это необходимо. Разумеется, также возможно и разумно для Population выводить из Container<int>, если он хранит некоторый другой набор чисел, но это не зависит от косвенно унаследованных контейнеров.

Мой вопрос заключается в том, что "это не является отношением is-a, я использовал naughtily используемое публичное наследование как синтаксическое удобство, когда оно не было концептуально корректным"

Я не вижу проблемы с отношениями или концептуальной достоверностью выше, если вы объясните, пожалуйста.

"Мне никогда не нужно полиморфно ссылаться на базовый класс из самого производного класса, поэтому невероятно небольшие накладные расходы не стоит"

Я думаю, что ясно, что я просто делаю моделирование данных на основе отношений с природными объектами, а не какую-то сомнительную оптимизацию. Вспомогательный контейнерный класс, укомплектованный из баз, фактически используется и должен быть независимым в каждом.

Ответ 2

Я бы сказал, что это скорее приведет к хаосу.

Давайте проигнорируем случай базового базового класса. Кажется очевидным, что наличие двух некогерентных состояний скорее всего вызовет путаницу и будет подвержено ошибкам.

Я бы предпочел сосредоточиться на проблеме идентичности. В С++ идентификатор объекта определяется его адресом. Именно по этой причине, кроме пустых базовых классов, каждый объект должен иметь по крайней мере размер одного байта.

Если у вас один и тот же базовый класс несколько раз в вашей иерархии, вы можете получить два Base*, которые ссылаются на один и тот же объект... и все же отличаются (указывают на разностный адрес).

Конечно, вы можете потянуть свои трюки и использовать dynamic_cast<void*>(p), чтобы получить "реальный" физический адрес всего объекта... но все же.

Ответ 3

Проблема - одно из тождеств; в случае iostream иерархии, общие базы даже имеют состояние, которое устанавливается манипуляторы. Если было несколько экземпляров basic_ios, у вас есть две копии состояния (форматирование флаги, состояние ошибки, даже streambuf), которые будут катастрофа.

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

Ответ 4

Пример

class Category
{   
public:
    virtual std::string CatName(); 
};

class OxygenBreath : public Category
{
public:
    virtual void Breath() = 0;
    std::string CatName(){ return "OxygenBreath";}
};

class LandWalk : public Category  
{
public:
    virtual void Walk() = 0;
    std::string CatName(){ return "LandWalk";}
};

class Human : public OxygenBreath, public LandWalk
{};

Ответ 5

Бывают случаи, когда это полезно. Скажите, что класс touchscreen получен из display_device и input_device? Теперь предположим, что для каждой базы общий базовый класс имеет поле estimated_power_consumption, не было бы полезно избежать виртуального наследования?

Ответ 6

Теоретически, я могу представить себе причину наследования "сестер" B и C из общих A практически не конфиденциально, а затем наследовать публикацию некоторого "сложного класса" D из A и B.

Это, конечно, не отношение is_a между большинством полученных (D) и большинством базового класса (A), поскольку частное наследование не показывает отношения is_a к пользователям и более поздним потомкам.

Однако, если классы сестер B и C специально не разработаны для этого случая (например, для наследования алмазов), они могут быть публично унаследованы от A.

Эффект почти такой же, как для частного наследования: мы не можем получить доступ к членам A из D ( [отредактировано: по крайней мере, без явного перевода в сестры; ] любая попытка будет неоднозначной). Таким образом, можно рассматривать публичное невиртуальное наследование как замену частному невиртуальному наследованию.

Но я бы никогда не делал таких трюков. Собственное наследование - полезная метафора, но не подвал Вселенной, а использование его тонких функций приводит к ненужному сложному дизайну.

IMO, если замена простого (невиртуального) наследования с виртуальным является (концептуально) неприемлемым, почти наверняка будет недостаток дизайна.

Итак, я бы ответил "нет" (несмотря на мою конструкцию "theretically speaking...":)

Ответ 7

struct managed
{
    report(std::string what);

private:
    manager_type manager;

protected:
    managed(manager_type);
};

struct human : managed
{
    human() : managed(god) {}
};

struct robot : managed
{
    robot(manager_type owner) : managed(owner) {}
};

struct employee : managed
{
    employee(manager_type boss) : managed(boss) {}
};

struct human_employee : human, employee
{
    human_employee(manager_type boss) : employee(boss) {}
};

struct robot_employee : robot, employee
{
    robot_employee(manager_type owner) : robot(owner), employee(owner) {}
};

Теперь рассмотрим:

void do_some_duty(const employee& e)
{
    e.do_some_tasks();
    e.report("done");
}

void face_disaster(const human& h)
{
    h.report("Oh my!");
}

void commit_suicide(const managed& m)
{
    m.report("I want to suicide");
}

и как человек-человек, тот, о котором вы сообщаете, отличается, если вы не работаете:

human_employee h;
if (h.at_work()) commit_suicide(static_cast<const employee&>(h));
else commit_suicide(static_cast<const human&>(h));

Я бы даже подумал об использовании такого дизайна, если мне действительно нужно. Вы можете представить себе вместо managed некоторый класс, который будет хранить ссылку на объект глобального менеджера, например. вывоз мусора. В этом случае действительно имеет смысл иметь различные базовые классы для базовых объектов.