【发布时间】:2015-04-27 08:08:41
【问题描述】:
我开始研究 CRTP 成语,我注意到 GCC 有一个 fdevirtualize 标志,它应该允许在可能的情况下将 vtable 调用转换为直接调用。
虽然 CRTP 可以与任何(C++ 兼容)编译器一起应用,但如果我只想使用 gcc 进行开发,我是否可以避免 CRTP 惯用语将去虚拟化过程留给 gcc,或者在可能的情况下使用它总是更好以便使用静态多态以避免虚函数调用?
【问题讨论】:
我开始研究 CRTP 成语,我注意到 GCC 有一个 fdevirtualize 标志,它应该允许在可能的情况下将 vtable 调用转换为直接调用。
虽然 CRTP 可以与任何(C++ 兼容)编译器一起应用,但如果我只想使用 gcc 进行开发,我是否可以避免 CRTP 惯用语将去虚拟化过程留给 gcc,或者在可能的情况下使用它总是更好以便使用静态多态以避免虚函数调用?
【问题讨论】:
去虚拟化是否有帮助,取决于编译器是否内联你的东西。
请注意,CRTP 通常用于实现 mixins 和模板方法模式,例如像这样:
template <typename T, typename Derived>
class pointer_base {
const Derived& self() const { return static_cast<const Derived&>(*this); }
public:
T& operator *() const { return *self().get(); }
T* operator ->() const { return self().get(); }
};
template <typename T>
class smart_pointer : public pointer_base<T, smart_pointer<T>> {
public:
T* get() const;
};
还有虚调用形式:
template <typename T>
class pointer_base {
protected:
~pointer_base() = default;
public:
virtual T* get() const = 0;
T& operator *() const { return *get(); }
T* operator ->() const { return get(); }
};
template <typename T>
class smart_pointer : public pointer_base<T> {
public:
T* get() const override;
};
两种情况下的用法都是
smart_pointer<int> p(new int);
*p = 42;
请注意,可能有多个不同的智能指针类。在虚函数版本中,它们都派生自同一个pointer_base。这意味着有一个版本的基本功能可以在所有这些之间共享。如果是这样的话,去虚拟化就行不通了。
只有当相关函数被内联到调用者时才会起作用,因为编译器可以将代码专门化为特定的具体智能指针类型。
当然,由于助手如此之小,它们被内联的机会很高。另一方面,您现在有一个指针基类,客户端可以看到并且可能会想使用它。您可以从基类私有派生,但随后必须为要在每个派生类中公开的所有成员添加 using 声明。对于像pointer_base这样的类,它包含很多小函数(在我的例子中是2个,但也应该有一个布尔转换),这很烦人。但是,CRTP 的语法混乱也是如此。我认为这归结为品味问题。
当您只有一个大模板方法时,编译器很可能不会内联它。在这种情况下,它不能去虚拟化。但是 CRTP 有其自身的缺点:无论您是否愿意,该方法都会为基础的每个实例化重复。这会导致代码膨胀。
所以它最终归结为模板与虚拟函数的常见问题:专用代码及其相关代码大小增加,或者虚拟调用的性能损失和它们设置的内联障碍。
【讨论】: