На самом деле это не работает. Попытайтесь протестировать его со случаем, где первое условие ПЕРЕСТАЛО РАБОТАТЬ, но вторые передачи. Вы найдете, что это возвратит 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 для конечного результата быть верными. Иметь смысл?
Давайте рассмотрим простой пример.
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 или концептуальной обоснованностью вышесказанного, если вы объясните, пожалуйста ...
«Мне никогда не нужно полиморфно ссылаться на базовый класс из самого производного класса, так что невероятно маленькие накладные расходы не стоят»
Я думаю, что ясно, что я просто моделирование данных, основанное на связях с естественными объектами, а не сомнительная оптимизация. Вспомогательный контейнерный класс, выделенный из баз, фактически используется и должен быть независимым в каждом.
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
некоторый класс, который, например, будет хранить ссылку на объект глобального менеджера. вывоз мусора. В этом случае действительно имеет смысл иметь разные базовые классы для базовых объектов.
Могут быть случаи, когда это полезно. Скажите класс touchscreen
, полученный из display_device
и input_device
? Теперь предположим, что у каждого базового strike> общего базового класса есть поле estimated_power_consumption
, не будет ли полезно избежать виртуального наследования?