Очень маловероятно. В стандарте нет ничего, чтобы останавливать компиляторы, делающие целые классы тупо неэффективных вещей, но не виртуальный вызов - все еще не виртуальный вызов, независимо от того, имеет ли класс также виртуальные функции. Он должен вызывать версию функции, соответствующую статическому типу, а не динамическому типу:
struct Foo {
void foo() { std::cout << "Foo\n"; }
virtual void virtfoo() { std::cout << "Foo\n"; }
};
struct Bar : public Foo {
void foo() { std::cout << "Bar\n"; }
void virtfoo() { std::cout << "Bar\n"; }
};
int main() {
Bar b;
Foo *pf = &b; // static type of *pf is Foo, dynamic type is Bar
pf->foo(); // MUST print "Foo"
pf->virtfoo(); // MUST print "Bar"
}
Так что для реализации абсолютно не нужно помещать не виртуальные функции в vtable, и, действительно, в vtable для Bar
вам понадобятся два разных слота в этом примере для Foo::foo()
и Bar::foo()
. Это означает, что vtable будет использоваться в особом случае, даже если реализация хотела бы сделать это. На практике он не хочет этого делать, не имеет смысла делать это, не беспокойтесь об этом.
Базовые классы CRTP действительно должны иметь не виртуальные и защищенные деструкторы.
Виртуальный деструктор необходим, если пользователь класса может взять указатель на объект, привести его к типу указателя базового класса, а затем удалить его. Виртуальный деструктор означает, что это будет работать. Защищенный деструктор в базовом классе останавливает их попытки (delete
не будет компилироваться, так как нет доступного деструктора). Таким образом, любой из виртуальных или защищенных решает проблему случайного провоцирования пользователем неопределенного поведения.
См. Правило № 4 здесь и обратите внимание, что «недавно» в этой статье означает почти 10 лет назад:
http://www.gotw.ca/publications/mill18.htm
Ни один пользователь не создаст собственный Base<Derived>
объект, который не является Derived
объектом, поскольку это не то, для чего предназначен базовый класс CRTP. Им просто не нужно иметь доступ к деструктору - так что вы можете оставить его вне общедоступного интерфейса или сохранить строку кода, вы можете оставить его открытым и полагаться на то, что пользователь не делает глупостей.
Причина, по которой нежелательно, чтобы он был виртуальным, поскольку ему это не нужно, заключается в том, что нет смысла давать классу виртуальные функции, если они им не нужны. Когда-нибудь это может стоить чего-то, с точки зрения размера объекта, сложности кода или даже (маловероятной) скорости, поэтому преждевременная пессимизация, чтобы сделать вещи виртуальными всегда. Предпочтительный подход среди программистов на С ++, использующих CRTP, заключается в том, чтобы абсолютно четко понимать, для чего предназначены классы, предназначены ли они вообще для базовых классов и, если да, предназначены ли они для использования в качестве полиморфных основ. Базовые классы CRTP не.
Причина, по которой у пользователя нет бизнес-приведения к базовому классу CRTP, даже если он общедоступный, заключается в том, что он на самом деле не предоставляет «лучший» интерфейс. Базовый класс CRTP зависит от производного класса, поэтому вы не будете переключаться на более общий интерфейс, если приведете Derived*
к Base<Derived>*
. Ни один другой класс никогда не будет иметь Base<Derived>
в качестве базового класса, если только он не имеет Derived
в качестве базового класса. Это просто бесполезно как полиморфная основа, так что не делайте это.