Предположим, у меня есть следующая иерархия, использующая идиому NVI:
class Base
{
public:
virtual ~Base() {}
void foo() { cout << "Base::foo" << endl; foo_impl(); }
private:
virtual void foo_impl() = 0;
};
class A : public Base
{
private:
virtual void foo_impl() { cout << "A::foo_impl" << endl; }
};
Если в какой-то момент в иерархии я хочу «добавить» инварианты в не виртуальный базовый метод, что будет лучшим способом сделать это?
Одним из способов было бы использовать идиому NVI на уровне SpecialBase:
class SpecialBase : public Base
{
private:
void foo_impl() { cout << "SpecialBase::foo" << endl; bar_impl(); }
virtual void bar_impl() = 0;
};
class B : public SpecialBase
{
private:
virtual void bar_impl() { cout << "B::bar_impl" << endl; }
};
Но мне не очень нравится эта идея, поскольку я не хочу добавлять методы (с разными именами) для каждой производной базы, которую я добавляю в свою иерархию …
Другим способом является следующее (что не является NVI):
class Base
{
public:
virtual ~Base() {}
virtual void foo() { base_foo(); foo_impl(); }
protected:
void base_foo() { cout << "Base::foo" << endl; }
virtual void foo_impl() = 0;
};
class SpecialBase : public Base
{
public:
virtual void foo() { base_foo(); specialbase_foo(); foo_impl(); }
protected:
void specialbase_foo() { cout << "SpecialBase::foo" << endl; }
};
class B : public SpecialBase
{
private:
virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};
Что, на мой взгляд, менее запутанно, поскольку в любой момент конкретный класс просто должен реализовать виртуальный метод, в то время как производный базовый класс может переопределить базовый (виртуальный) метод, если он тоже выберет.
Есть ли другой более чистый способ добиться того же?
РЕДАКТИРОВАТЬ:
Я ищу очень общий шаблон проектирования, который мог бы позволить мне иметь следующий вид иерархии:
Base <- A
<- B
<- SpecialBase <- C
<- D
<- VerySpecialBase <- E
<- StrangeBase <- F
Где каждый Base
класс может (и переопределит foo), тогда как классы A-F
нужно будет только переопределить foo_impl
,
Обратите внимание, что просто добавив еще одну необязательную виртуальную функцию настройки (например, bar_impl
) здесь не поможет, потому что он допускает только один дополнительный уровень настройки, где мне может понадобиться бесконечное число.
В моем понимании, NVI — это способ предотвратить / препятствовать добавлению инвариантов к невиртуальному базовому методу, поэтому тот факт, что вы хотите добавить инварианты на этом этапе, говорит о том, что NVI либо вовсе не тот шаблон, который вы ищете, или вы можете реструктурировать свой дизайн, чтобы вам не нужно было добавлять такие инварианты.
Это означает, что альтернативой простому превращению ранее не виртуального интерфейса в виртуальное было бы использование последнего ключевого слова из C ++ 11:
class Base
{
public:
virtual ~Base() {}
virtual void foo() { base_foo(); foo_impl(); }
protected:
void base_foo() { cout << "Base::foo" << endl; }
virtual void foo_impl() = 0;
};
class SpecialBase : public Base
{
public:
virtual void foo() final // note the use of 'final'
{ base_foo(); specialbase_foo(); foo_impl(); }
protected:
void specialbase_foo() { cout << "SpecialBase::foo" << endl; }
};
class B : public SpecialBase
{
private:
virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};
Здесь NVI не реализован классом Base, но реализован на уровне SpecialBase, так как классы, производные от SpecialBase, больше не могут переопределять открытый интерфейс (а именно, foo).
Таким образом, мы говорим, что общедоступный интерфейс Base может быть переопределен (могут быть добавлены инварианты или даже вся функция может быть переопределена), но общедоступный интерфейс SpecialBase — нет.
Лично я считаю, что это может быть полезно в некоторых ограниченных случаях, но большую часть времени я просто хотел более полный интерфейс в Base.
В конечном счете, я думаю, что более широко используется Base, чтобы четко определить, какие пункты настройки разрешены:
class Base
{
public:
virtual ~Base() {}
virtual void foo() { base_foo(); bar_impl(); foo_impl(); }
protected:
void base_foo() { cout << "Base::foo" << endl; }
virtual void bar_impl() {} // bar_impl is an optional point of customization
// by default it does nothing
virtual void foo_impl() = 0; // foo_impl is not optional derived classes
// must implement foo_impl or else they will be abstract
};
class B : public Base
{
private:
virtual void bar_impl() { cout << "SpecialBase::foo" << endl; }
virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};
Обратите внимание, что больше нет необходимости в слое класса SpecialBase вообще.
Этот пост был предложен мне как похожий на то, что я просматривал, связанный с NVI на днях, отсюда и некро.
Я бы предложил добавить механизм проверки-добавления в базовый класс, чтобы производные классы могли добавлять требования. Это работает очень просто, если требования можно протестировать с помощью функций доступа к базовому классу, в противном случае ваш специальный класс MyInvariant должен динамически передавать базовый аргумент doCheckInvariantOK (), чтобы инвариант работал.
редактировать: я понимаю, что «инвариант» соответствует предварительным и постусловиям функции foo (), как при формальной проверке. Если вы хотите добавить функциональность до и / или после base_foo (), то, что я думаю, что вы на самом деле после, вы можете сделать это аналогичным образом.
class Base
{
public:
virtual ~Base() {}
void foo()
{
cout << "Base::foo" << endl;
//Can use invariants as pre and/or postconditions for foo_impl
for(const std::unique_ptr<InvariantBase>& pInvariant : m_invariants)
{
//TODO cout << "Checking precondition " << pInvariant->GetDescription() << endl;
if(!pInvariant->CheckInvariantOK(*this))
{
//Error handling
}
}
foo_impl();
}
protected:
void AddInvariant(std::unique_ptr<InvariantBase>&& pInvariant)
{
m_invariants.push_back(std::move(pInvariant));
}
struct InvariantBase
{
bool CheckInvariantOK(const Base& base)
{
return doCheckInvariantOK(base);
}
private:
virtual bool doCheckInvariantOK(const Base& base) = 0;
};
private:
std::list<std::unique_ptr<InvariantBase>> m_invariants;
virtual void foo_impl() = 0;
};
class A : public Base
{
private:
virtual void foo_impl() { cout << "A::foo_impl" << endl; }
};
class SpecialBase : public Base
{
public:
SpecialBase()
: Base()
{
AddInvariant(std::unique_ptr<MyInvariant>(new MyInvariant() ) );
}
private:
void foo_impl() { cout << "SpecialBase::foo" << endl; bar_impl(); }
virtual void bar_impl() = 0;
struct MyInvariant : public InvariantBase
{
virtual bool doCheckInvariantOK(const Base& base) override
{
//TODO: special invariant code
}
};
};
class B : public SpecialBase
{
private:
virtual void bar_impl() { cout << "B::bar_impl" << endl; }
};