抽象类作为接口,不带虚函数表
Abstract class as an interface, without the vtable
我想创建一个抽象类,定义一个类的一些方法。其中一些应该由基类(base)实现,一些应该在base中定义但被Derived覆盖,其他应该在base中纯虚的以强制在Derived中定义。
这当然是抽象类的作用。然而,我的应用程序只会直接使用派生对象。因此,编译器应该在编译时准确地知道要使用哪些方法。
现在,因为这段代码将在RAM非常有限的微控制器上运行,所以我希望避免实际使用虚函数表的虚拟类。从我的测试来看,编译器似乎足够聪明,除非必须,否则不会创建虚变量表,至少在某些情况下是这样。然而,我被告知永远不要相信编译器:有可能使这成为编译的必要条件吗?
下面是一些代码示例: 类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();
}
是vtable 1
由于我的草率编码,我错误地迫使编译器使用多态性,因此需要一个虚函数表。如何使这种情况抛出错误?
int main() {
Base * d;
d = new Derived();
d->thisMustBeDefined();
}
是vtable 2
这里我没有在任何时候引用类"Base",所以编译器应该知道所有的方法都是在编译时预先确定的。但是,它仍然创建一个虚函数表。这是另一个例子,说明为什么我希望能够通过编译错误检测到这一点。
int main() {
Derived * d;
d = new Derived();
d->thisMustBeDefined();
}
换句话说,如果我编写的代码导致编译器为我的类生成虚函数表,即使用多态性,我希望它是一个编译器错误。
正如在评论中已经提到的,您可以使用CRTP(又名静态多态性)来避免创建虚值表:
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在他们的评论中指出的那样,将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
可以使用已删除函数来要求在派生类中定义函数,但请注意,只有在程序中使用该函数时才有效。
template <typename Der>
class Base {
public:
int thisMustBeDefined() = delete; // 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/oz9ooe7Yc
- 内联如何影响模块接口中的成员函数
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- unique_ptr实现接口时对已删除函数的引用
- 在多个头文件中从接口声明被覆盖的函数时,如何避免重复代码?
- 如何通过接口将函子分配给函数对象
- 如何创建一个接口,允许我访问C++中的按钮(和其他ui)函数,该函数是使用python中的MFC实现的
- C++接口的工厂函数实现
- 对已定义的接口析构函数的未定义引用
- 必须具有泛型接口的函数,但必须根据传递的子类(不知道它们是什么!)以不同的行为 - C++
- 在构造函数处将类对象强制转换为接口始终返回 NULL
- 在接口文件中使用模板时出现"not a type"错误的函数指针
- 从 COM 接口中的函数返回多个值
- 为什么在将多态行为与指向接口的指针一起使用时没有调用析构函数?
- 这是重载提供与非静态成员函数相同接口的静态成员函数的优雅方法吗?
- 从多个不同的实现类 c++ 调用接口函数
- 单冒号是什么意思 c++ 函数接口
- 编写现代函数接口以"produce a populated container"
- 为什么std::vector的构造函数接口在C++11中发生了变化
- 传递函数接口函数的指针
- 为什么shared_ptr无法解析函数接口中的继承关系?