我想创建一个抽象类来定义类的一些方法。其中一些应该由基类(Base)实现,一些应该在 Base 中定义但被 Derived 覆盖,另一些应该是 Base 中的纯虚拟以强制在 Derived 中定义。
这当然就是抽象类的用途。但是,我的应用程序只会直接使用 Derived 对象。因此,编译器应该在编译时确切地知道要使用哪些方法。
现在,因为此代码将在 RAM 非常有限的微控制器上运行,所以我热衷于避免实际使用带有 vtable 的虚拟类。从我的测试来看,编译器似乎足够聪明,除非必须,否则不会创建 vtable,至少在某些情况下是这样。然而我被告知永远不要相信编译器:是否可以将此作为编译的必需条件?
以下是一些代码示例:
class Base {
public:
Base() {}
virtual ~Base() {};
virtual int thisMustBeDefined() = 0;
virtual int thisCouldBeOverwritten() { return 10; }
int thisWillBeUsedAsIs() { return 999; }
};
class Derived : public Base {
public:
Derived() {}
~Derived() {}
int thisMustBeDefined() { return 11; }
};
这没有 vtable,这就是我想要的
int main() {
Derived d;
d.thisMustBeDefined();
}
由于我的草率编码,我错误地强制编译器使用多态性,因此需要一个虚函数表。我怎样才能让这个案例抛出错误?
int main() {
Base * d;
d = new Derived();
d->thisMustBeDefined();
}
这里我在任何时候都没有引用类“Base”,因此编译器应该知道所有方法都是在编译时预先确定的。然而它仍然创建一个虚函数表。这是为什么我希望能够通过编译错误检测到这一点的另一个例子。
int main() {
Derived * d;
d = new Derived();
d->thisMustBeDefined();
}
换句话说,如果我编写的代码导致编译器为我的类生成 vtable,即使用多态性,我希望它成为编译器错误。
正如评论中已经提到的,您可以使用 CRTP (又名静态多态性)来避免创建 vtable:
template <typename Der>
class Base {
public:
Base() {}
~Base() {};
int thisMustBeDefined() {
// Will fail to compile if not declared in Der
static_cast<Der*>(this)->thisMustBeDefined();
}
int thisCouldBeOverwritten() { return 10; }
int thisWillBeUsedAsIs() { return 999; }
};
class Derived : public Base<Derived> {
public:
Derived() {}
~Derived() {}
int thisMustBeDefined() { return 11; }
// Works since you call Derived directly from main()
int thisCouldBeOverwritten() { return 20; }
};
如果某个函数未在
Derived
中实现,为了使编译器错误更具可读性,您可以使用简单的静态检查,如此答案中提供的:
#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature) \
template <typename U> \
class traitsName \
{ \
private: \
template<typename T, T> struct helper; \
template<typename T> \
static std::uint8_t check(helper<signature, &funcName>*); \
template<typename T> static std::uint16_t check(...); \
public: \
static \
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
}
DEFINE_HAS_SIGNATURE(thisMustBeDefined, T::thisMustBeDefined, int(*)(void));
并将静态检查添加到
Base
构造函数中:
Base() {
static_assert(thisMustBeDefined<Der>::thisMustBeDefined,
"Derived class must implement thisMustBeDefined");
}
虽然在小型设备上工作时应该考虑的一个缺点,并且一次有多个版本的
Derived
是Base
中的代码将为每个 Derived
实例重复。
因此,您必须决定对于您的用例来说更重要的限制是什么。
正如 @ChrisDrew 在他们的 comment 中指出的那样,将
thisCouldBeOverwritten()
和 thisWillBeUsedAsIs()
函数移动到 Base
模板类派生的另一个基类将有助于解决这个问题。
正如 πάντα ῥεῖ 的回答中提到的,CRTP 就是这里的解决方案。 但更正一下: 方法
thisMustBeDefined
实际上并不要求 Derived 实现它。
int thisMustBeDefined() {
// Incorrect theory: Will fail to compile if not declared in Der
static_cast<Der*>(this)->thisMustBeDefined();
}
参见 godbolt https://godbolt.org/z/x6o1T6jn1
您可以使用
static_assert(false)
来要求在派生类中定义函数,但请注意,它仅在程序中使用该函数时才有效。
template <typename Der>
class Base {
public:
int thisMustBeDefined() {
static_assert(false); // Derived class must define this function
}
};
class Derived : public Base<Derived> {
public:
// int thisMustBeDefined() { return 11; } // LINE A
};
int main() {
Derived d;
d.thisMustBeDefined(); // LINE B
}
// Compile error will occur if (LINE A does not exist) && (LINE B exists),
参见 godbolt https://godbolt.org/z/sPona3aeY