Я оправдываю множественное наследование в конкретном случае?
В настоящее время у меня есть следующие классы, управляющие различными видами переменных:
class Variable;
class Number : public Variable;
class Boolean : public Variable;
class RealNumber : public Number;
class IntegerNumber : public Number;
Это классическое дерево наследования, и оно хорошо работает. Я управляю векторами Number*
экземпляры и векторы Boolean*
экземпляров.
Я хотел бы добавить другой тип переменной: "виртуальная переменная" с определенным поведением, но с похожим интерфейсом. Затем я хочу работать с векторами "виртуальных чисел" и векторами "виртуальных логических значений".
Первое решение - добавить флаг в Variable
, но я предпочитаю компиляцию безопасности и управления std::vector<VirtualResponse*>
а также std::vector<VirtualBoolean*>
чтобы иметь гарантию, переменные являются виртуальными в векторе.
Затем, я думаю, может быть, я нахожусь в конкретном случае, когда множественное наследование оправдано, но я новичок с множественным наследованием. Что вы думаете об этом классе?
class VirtualVariable : public virtual Variable;
class VirtualNumber : public virtual Number, public virtual VirtualVariable;
class VirtualBoolean : public virtual Boolean, public virtual VirtualVariable;
class VirtualRealNumber : public virtual RealNumber, public virtual VirtualNumber;
class VirtualIntegerNumber : public virtual IntegerNumber, public virtual VirtualVariable;
Это классический способ сделать это? Может ли это быть ловушкой? Я злоупотребляю virtual
ключевое слово?
РЕДАКТИРОВАТЬ: пример того, что я хочу сделать:
void my_function(const std::vector<SpecItem>& spec)
{
// First : create the description from the spec
std::vector<Number*> descr;
std::vector<VirtualNumber*> virt_descr;
for(unsigned long int i = 0; i < spec.size(); ++i)
{
if(spec[i].isNumber())
if(spec[i].isReal())
{
double lower_bound = spec[i].lowerBound();
double upper_bound = spec[i].upperBound();
if(spec[i].isVirtual())
{
std::string formula = spec[i].formula();
virt_descr.push_back(new VirtualRealNumber(lower_bound,upper_bound,formula));
}
else
descr.push_back(new RealNumber(lower_bound,upper_bound));
}
else if(spec[i].isInteger())
{
long int lower_bound = ceil(spec[i].lowerBound());
long int upper_bound = floor(spec[i].upperBound());
if(spec[i].isVirtual())
{
std::string formula = spec[i].formula();
virt_descr.push_back(new VirtualIntegerNumber(lower_bound,upper_bound,formula));
}
else
descr.push_back(new IntegerNumber(lower_bound,upper_bound));
}
}
// Now, descr is a vector of Number*, virt_descr is a vector of VirtualNumber*
// Second step : assign values to the numbers
std::vector<double> values;
for(unsigned long int i = 0; i < descr.size(); ++i)
{
double new_value = (descr[i]->lowerBound() + descr[i]->upperBound()) / 2.0;
values.push_back(descr[i]->adjust(new_value));
}
// Third step : evaluate virtual numbers
std::vector<double> virt_values;
for(unsigned long int i = 0; i < virt_descr.size(); ++i)
{
double new_value = virt_descr[i]->evaluate(values);
values.push_back(descr[i]->adjust(new_value));
}
return 0;
}
2 ответа
Множественное наследование призвано способствовать чистому дизайну, обеспечивая разделение интересов. Так что это может быть совершенно оправдано.
Трудно посоветовать лучший дизайн класса, не зная больше о причинах и ограничениях. Но есть некоторые вопросы, которые могут вам помочь.
Начинается с линии:
class VirtualVariable : public virtual Variable;
1) Вам нужно виртуальное наследование?
Множественное наследование действительно просто и весело, пока вы не перейдете к виртуальному наследованию. Это означает, что для класса, наследующего несколько раз от VirtualVariable
все VirtualVariable
экземпляры подобъекта будут совместно использовать один Variable
Базовый объект. Для получения дополнительной информации читайте о проблеме алмазов.
В вашем дизайне это может понадобиться для каждого наследования Variable
(в том числе для не виртуальных), чтобы избежать нескольких Variable
подобъекты, где у вас действительно должен быть только один. Вам не нужно это для другого наследства.
Это виртуальное наследование потребует, чтобы для каждого конструктора вы явно инициализировали свою виртуальную базу.
2) Какова реальная связь между Variable
а также VirtualVariable
?
Согласно вашему объяснению, я понял, что понятие "виртуальность" не зависит от понятия "переменная". С другой стороны, в своем коде вы делаете вид, что VirtualVariable
это Variable
,
Если это что-то независимое, вы должны сохранить его независимым. Это было бы лучшим разделением проблем. Хорошей новостью является то, что вам больше не нужно виртуальное наследование:
class Virtuality;
class VirtualVariable : public Variable, public Virtuality;
class VirtualNumber : public Number, public Virtuality;
class VirtualBoolean : public Boolean, public Virtuality;
class VirtualRealNumber : public RealNumber, public Virtuality;
class VirtualIntegerNumber : public IntegerNumber, public Virtuality;
3) Вы смотрели на шаблоны?
Вы сказали:
Первое решение - добавить флаг в Variable, но я предпочитаю безопасность компиляции и управление
Если это только для проверки типов компиляции, вы можете использовать шаблоны:
template <bool isvirtual>
Variable { ...};
template <bool isvirtual>
class Number : Variable<isvirtual> { ...};
...
std::vector<Number<true>*> v;
Для более сложных вещей, чем просто переменная, вы можете использовать дизайн на основе политик. Грубо говоря, вместо предоставления bool в качестве параметра шаблона вы предоставляете служебный класс в качестве параметра, который инкапсулирует семейство поведений.
РЕДАКТИРОВАТЬ: для вашего редактирования
Решения 2 и 3 должны быть в состоянии реализовать то, что вы собираетесь делать.
Я разместил здесь онлайн-демонстрацию решения 2 с полиморфизмом, чтобы продемонстрировать, как легко можно смешивать "виртуальные" и не "виртуальные" элементы. Определяет функцию IntegerNumber add (IntegerNumber &a, IntegerNumber&b);
и использует его в этом сценарии:
IntegerNumber n(27); // number with an inital value
VirtualIntegerNumber k; // value will be calculated from some formula
add (n, k); // ok, my demo just displays the value of each parameter ;-)
* Кстати, я думаю, что конструкции if (isxxx())... if (isyyy()) ...
в вашем коде настоятельно рекомендуем использовать полиморфизм.
Я стараюсь избегать множественного наследования, насколько это возможно. Звучит так, будто вы хотите объект, который действует как базовый объект, но с изменениями для конкретных случаев. Походит на спецификацию класса. Я не уверен, какова ваша цель, но, возможно, вы можете изменить свой дизайн на классическую модель иерархии. Для этого сделайте весь конкретный код в отдельных функциях и затем вызовите его в базовом классе.
class Number : public Variable
{
...
protected:
virtual void functionWithSpecialUsage() { // Empty in base class, used by inheritance only };
}
class VirtualNumber : public Number
{
...
protected:
void functionWithSpecialUsage() { // Add class specific behavior here };
}