c++ - 使用抽象基类为模板参数类型强制使用公共接口是不好的做法吗?
问题描述
我是模板编程的新手,偶然发现了这个成语。例如,类似:
class FooBase {
public:
virtual void do_something() = 0;
};
template <class Foo> // Foo is derived from FooBase
void g(Foo& foo) {
static_assert(std::is_base_of<FooBase, Foo>::value);
// ...
foo.do_something();
// ...
}
在我自己看来,这种模式很有用,因为:
- 纯虚函数声明具有显式签名;与 C++20 概念
requires
子句(如果有的话)相比,它很容易指定属性、参数和返回类型。 - 这是很好的文档;
FooBase
只需阅读标题就可以清楚地了解定义新 Foo 类的要求。 - 可以将所有 Foo 的通用功能重构到同一个
FooBase
类中。
但是,我担心使用virtual
函数对性能的影响。我的理解是运行时没有成本,因为函数是从派生类调用的;但链接器将无法内联该函数。
另一个缺点是不允许使用虚拟模板功能。
最后,我担心这可能是一种代码异味,因为它使用运行时多态的特性来执行静态检查。C++20 概念可能是执行此操作的“正确”方法,但由于上述三个原因,它们似乎不太方便。
解决方案
这是不好的做法,因为您使用了错误的工具来完成这项工作。您正在编写传达错误信息的代码,并且没有实际的执行机制。
动态多态性的关键在于它是动态的:在运行时定义,这样您就可以将对象传递给不完全知道它们在编译时给出的类型的函数。这允许针对基类编写一个位置的代码,而不是将其导出到标头等中。并且在任何时候使用的实际类也不能导出到标题等中。只有类的来源需要知道实际的类型。
像模板这样的编译时多态性是基于在编译时知道一切的。所有代码,包括源代码和目标代码,都需要知道类型是什么。
本质上,与编译时多态性相比,您喜欢动态多态性阐明其要求的方式,但您试图通过使用编译时多态性来回避它的性能成本。当您混淆机制时,这会造成代码混乱。
如果有人看到带有virtual
函数的基类,他们会假设您的代码会将指针/引用传递给周围的实际类。这是在大多数情况下使用它们的期望的一部分(即使virtual
基于 - 的类型擦除也有效地做到了这一点)。相反,看到一堆按值获取类型的模板函数会令人困惑。
此外,您没有执行机制。采用 a 的函数FooBase&
可确保用户无法使用不是实际FooBase
派生类型的类型调用它(好吧,您可以将其隐式转换为 one,但我们在这里忽略不忠)。您的模板函数,避开概念,没有类似的强制执行。您可以记录它必须是FooBase
- 派生类型,但您不会静态地强制执行它。
至少,模板参数应该声明为std::derived_from<FooBase> Foo
(不,static_assert
不是一个好主意)。
但实际上,您应该只使用正确的概念。您需要编译时多态性,无论您对概念的个人感觉如何,概念都是C++20 用于定义编译时多态性原型的语言机制。如果您使用概念,没有人会对您的代码的含义和意图感到困惑。
推荐阅读
- sql - 在 oracle 中,具有多个连接的查询执行需要更多时间
- android - 单击当前项目时更改适配器中的下一个项目背景颜色
- android - 在 React Native App 中禁用屏幕捕获/ScreenShot
- php - 如何在 PHP 中使用 Session(XML 307 响应)处理 API
- postgresql - 如何使用 lib/pq 将 hstore 对象插入到 postgres
- go - 为什么所有的 goroutine 都处于休眠状态?
- javascript - DevTools 与页面断开连接,电子
- excel - Microsoft Excel VBA Vlookup 不断给出 #N/A 错误
- javafx - JavaFX 颜色与 HSL Web 定义
- apache-spark - 在 JAVA 中使用 Spark 2.1.1 读取嵌套 Json(Spark 2.2 有解决方案,但我正在研究 spark 2.1.1 版本)