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

На самом деле это не работает. Попытайтесь протестировать его со случаем, где первое условие ПЕРЕСТАЛО РАБОТАТЬ, но вторые передачи. Вы найдете, что это возвратит true. Причина, потому что при контакте с многоадресными делегатами, которые возвращают значения, только последнее значение возвращено. Например:

        Func<string, bool> predicate = null;
        predicate += t => t.Length > 10;
        predicate += t => t.Length < 20;

        bool b = predicate("12345");

Это возвратит TRUE, потому что последний вызов функции возвращает true (это - меньше чем 20). Чтобы действительно заставить его работать, необходимо звонить:

predicate.GetInvocationList();

, который возвращает массив делегатов. Затем необходимо удостовериться, что они ВСЕ возвращают true для конечного результата быть верными. Иметь смысл?

23
задан Daniel Daranas 2 August 2013 в 16:45
поделиться

3 ответа

Давайте рассмотрим простой пример.

   B           B1   B2
   |             \ /
   D              D

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

Справа оба B1 и B2 являются независимыми основаниями D, и к D можно получить полиморфный доступ с помощью B1* или B2* (или ссылок). Надеемся, что мы можем принять, что это также допустимый ОО-проект (несмотря на то, что 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, возможно, с другим набором вспомогательных функций. Очевидно, что 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-with с Container<int>, и код может нуждаться в явном устранении неоднозначности. Это уместно. Конечно, для Population также возможно и разумно получить из Container<int>, если он хранит некоторый другой набор чисел, но это будет независимо от косвенно унаследованных контейнеров.

Мой вопрос, за исключением того, что «Это не совсем отношения, я использовал непослушное публичное наследование как синтаксическое удобство, когда оно не было концептуально обоснованным»

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

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

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

3
ответ дан 29 November 2019 в 03:12
поделиться
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));

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

0
ответ дан 29 November 2019 в 03:12
поделиться

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

0
ответ дан 29 November 2019 в 03:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: