Как уже было сказано, NVI - это идиома программирования, относящаяся к категории языков.Он был продвинут Хербом Саттером среди других, потому что он помогает обеспечить соблюдение контрактов:
- инварианты класса
- контракты функций (утверждения относительно переданных параметров и сгенерированного возвращаемого значения)
- повторяющиеся операции (например, ведение журнала)
- контроль над сгенерированными исключениями (плохая идея;))
Однако реализация может на самом деле значительно отличаться, например, другой пример NVIреализация состоит в том, чтобы объединить его с Pimpl:
class FooImpl;
class Foo
{
public:
enum type { Type1, Type2 };
Foo(type t, int i, int j);
int GetResult() const;
private:
FooImpl* mImpl;
};
А для реализации:
struct FooImpl
{
virtual ~FooImpl();
virtual int GetResult() const;
};
class FooType1: public FooImpl
{
public:
FooType1(int i, int j);
virtual int GetResult() const;
private:
/// ...
};
Я всегда обнаруживал, что это лучше передает смысл.Вы поняли это?
Суть в том, что virtual
- это деталь реализации.А показ деталей реализации в интерфейсе - плохая идея, потому что вы можете захотеть изменить их.
Более того, детали реализации имеют тенденцию путаться с двоичной совместимостью.Например, добавление нового метода virtual
в класс может изменить макет виртуальной таблицы (общая методика реализации) и таким образом нарушить двоичную совместимость.На gcc вы должны убедиться, что добавляете его последним (среди виртуальных), если хотите сохранить совместимость.
При использовании вышеуказанной комбинации NVI + Pimpl virtual
вообще нет (недаже личное) в классе выставлено.Расположение памяти обратно и вперед совместимо.Мы достигли бинарной совместимости.
Здесь мы используем несколько шаблонов одновременно:
- Шаблонный метод
- Стратегия (поскольку мы можем менять указатель по желанию)
- Фабрика (чтобы решить, какую реализацию мы получим)