使用抽象基础 class 来强制模板参数类型的通用接口是不好的做法吗?
Is it bad practice to use an abstract base class to enforce a common interface for a template parameter type?
我是模板编程的新手,偶然发现了这个习语。例如,类似于:
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
子句(如果可用)相比,指定属性、参数和 return 类型很容易。
- 这是很好的文档;只需阅读
FooBase
header. 就可以清楚定义新 Foo class 的要求
- 可以将所有 Foo 的通用功能重构为相同的
FooBase
class。
不过,我担心使用 virtual
函数对性能的影响。我的理解是在运行时没有成本,因为该函数是从派生的 class; 调用的。但链接器将无法内联该函数。
另一个缺点是不允许虚拟模板函数。
最后,我担心这可能是一般的代码味道,因为它使用 runtime-polymorphism 的功能来执行静态检查。 C++20 概念可能是执行此操作的“正确”方法,但由于上述三个原因,它们似乎不太方便。
这是不好的做法,因为您使用了错误的工具来完成这项工作。您正在编写传达错误内容的代码,并且没有实际的执行机制。
动态多态性的要点在于它是 动态的:在运行时定义,这样您就可以将 objects 传递给不完全了解其类型的函数在编译时重新给出。这允许一个位置的代码针对基础 class 编写,而不是导出到 headers 等。并且任何时候实际使用的 class 也不能导出为 headers 等。只有 class 的来源需要知道实际类型。
Compile-time 像模板这样的多态性是基于 compile-time 的一切知识。所有代码,包括源代码和目标代码,都需要知道类型是什么。
本质上,与 compile-time 多态性相比,您喜欢动态多态性阐明其要求的方式,但您尝试通过使用 compile-time 多态性来 side-step 它的性能成本。当您混淆机制时,这会造成代码混乱。
如果有人看到带有 virtual
函数的基础 class,他们会假设您的代码会将 pointers/references 传递给周围的实际 classes .这是在大多数情况下使用它们的期望的一部分(即使基于 virtual
的类型擦除也能有效地做到这一点)。相反,看到一堆按值获取类型的模板函数会令人困惑。
此外,您没有执行机制。采用 FooBase&
的函数确保用户不能使用不是实际 FooBase
派生类型的类型调用它(好吧,您可以将其隐式转换为一个,但让我们忽略这里的背信弃义).您的模板函数避开了概念,没有类似的强制执行。您可以记录它必须是 FooBase
派生类型,但您不会静态强制执行它。
至少,模板参数应该声明为 std::derived_from<FooBase> Foo
(不,static_assert
不是一个好主意)。
但实际上,您应该使用适当的概念。您需要 compile-time 多态性,无论您对概念的个人感受如何,概念都是 C++20 语言机制,用于定义 compile-time 多态性的原型。如果您使用概念,没有人会对您的代码的含义和意图感到困惑。
我是模板编程的新手,偶然发现了这个习语。例如,类似于:
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
子句(如果可用)相比,指定属性、参数和 return 类型很容易。 - 这是很好的文档;只需阅读
FooBase
header. 就可以清楚定义新 Foo class 的要求
- 可以将所有 Foo 的通用功能重构为相同的
FooBase
class。
不过,我担心使用 virtual
函数对性能的影响。我的理解是在运行时没有成本,因为该函数是从派生的 class; 调用的。但链接器将无法内联该函数。
另一个缺点是不允许虚拟模板函数。
最后,我担心这可能是一般的代码味道,因为它使用 runtime-polymorphism 的功能来执行静态检查。 C++20 概念可能是执行此操作的“正确”方法,但由于上述三个原因,它们似乎不太方便。
这是不好的做法,因为您使用了错误的工具来完成这项工作。您正在编写传达错误内容的代码,并且没有实际的执行机制。
动态多态性的要点在于它是 动态的:在运行时定义,这样您就可以将 objects 传递给不完全了解其类型的函数在编译时重新给出。这允许一个位置的代码针对基础 class 编写,而不是导出到 headers 等。并且任何时候实际使用的 class 也不能导出为 headers 等。只有 class 的来源需要知道实际类型。
Compile-time 像模板这样的多态性是基于 compile-time 的一切知识。所有代码,包括源代码和目标代码,都需要知道类型是什么。
本质上,与 compile-time 多态性相比,您喜欢动态多态性阐明其要求的方式,但您尝试通过使用 compile-time 多态性来 side-step 它的性能成本。当您混淆机制时,这会造成代码混乱。
如果有人看到带有 virtual
函数的基础 class,他们会假设您的代码会将 pointers/references 传递给周围的实际 classes .这是在大多数情况下使用它们的期望的一部分(即使基于 virtual
的类型擦除也能有效地做到这一点)。相反,看到一堆按值获取类型的模板函数会令人困惑。
此外,您没有执行机制。采用 FooBase&
的函数确保用户不能使用不是实际 FooBase
派生类型的类型调用它(好吧,您可以将其隐式转换为一个,但让我们忽略这里的背信弃义).您的模板函数避开了概念,没有类似的强制执行。您可以记录它必须是 FooBase
派生类型,但您不会静态强制执行它。
至少,模板参数应该声明为 std::derived_from<FooBase> Foo
(不,static_assert
不是一个好主意)。
但实际上,您应该使用适当的概念。您需要 compile-time 多态性,无论您对概念的个人感受如何,概念都是 C++20 语言机制,用于定义 compile-time 多态性的原型。如果您使用概念,没有人会对您的代码的含义和意图感到困惑。