2

假设我有以下代码:

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->foo_impl(); //A
      (*static_cast<Derived*>(this)).foo_impl(); //B
   }
};

class Derived : public Base<Derived> {
private:
   void foo_impl() {
      bar();
   }
};

几个问题:

A行会产生虚函数调用吗?尽管我在互联网上可以找到的大部分内容都建议以这种方式进行操作,但对我来说,考虑到指向 Derived 的指针实际上仍可能指向 Derived2 类型的对象,我看不到编译器如何进行静态调度:公共派生。

B 行是否解决了我在上一点中提出的问题(如果适用)?考虑到现在调用不再在指针上,因此使用 *. 将避免虚函数调用。但是如果编译器将取消引用的强制转换视为引用类型,它仍然可以生成虚函数调用......在这种情况下,解决方法是什么?

将 C++11 final 关键字添加到 foo_impl() 是否会改变编译器在任一(或任何其他相关)情况下的行为方式?

4

3 回答 3

5

A行会产生虚函数调用吗?

的。foo_impl()是虚拟的并Derived覆盖它。即使foo_impl()inDerived没有明确标记为virtual,它在基类中,这足以使其成为虚函数。

B 行是否解决了我在上一点中提出的问题(如果适用)?

没有。调用是在指针上还是在引用上都没有关系:编译器仍然不知道您是在派生自foo_impl()的类的实例上调用函数,还是在 的直接实例上调用函数。因此,调用是通过 vtable 执行的。 DerivedDerived

看看我的意思:

#include <iostream>

using namespace std;

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->foo_impl();
      (*static_cast<Derived*>(this)).foo_impl();
   }
};

class Derived : public Base<Derived> {
public:
   void foo_impl() {
      cout << "Derived::foo_impl()" << endl;
   }
};

class MoreDerived : public Derived {
public:
   void foo_impl() {
      cout << "MoreDerived::foo_impl()" << endl;
   }
};

int main()
{
    MoreDerived d;
    d.foo(); // Will output "MoreDerived::foo_impl()" twice
}

最后:

是否添加 C++11 final 关键字来foo_impl()更改编译器在任一(或任何其他相关)情况下的行为方式?

理论上,是的。该final关键字将使得无法在 的子类中覆盖该函数Derivedfoo_impl()因此,当通过指向 的指针执行函数调用时Derived,编译器可以静态解析调用。但是,据我所知,C++ 标准并不要求编译器这样做。

结论

无论如何,我相信你真正想做的不是在基类中声明foo_impl()函数。当您使用 CRTP 时,通常会出现这种情况。此外,如果您希望它访问的function ,则必须声明 class Base<Derived>a 。否则,您可以公开。friendDerivedDerivedprivatefoo_impl()foo_impl()

于 2013-02-05T16:54:26.337 回答
3

CRTP 的常见习惯用法不涉及在基中声明纯虚函数。正如您在其中一条评论中提到的那样,这意味着编译器不会强制定义派生类型中的成员(除了通过use,如果foo在基类中有任何使用,则需要在foo_impl派生类型中存在类型)。

虽然我会坚持常用的习惯用法并且不在基础中定义纯虚函数,但是,如果您真的觉得需要这样做,您可以通过添加额外的限定来禁用动态调度:

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->Derived::foo_impl();
      //                           ^^^^^^^^^
   }
};

额外限定条件的使用Derived::禁用动态调度,并且该调用将静态解析为Derived::foo_impl. 请注意,这将出现所有常见的警告:您有一个具有虚函数的类并为每个对象支付虚指针的成本,但是您不能在最派生的类型中覆盖该虚函数,就像在 CRTP 基础中的使用一样正在阻止动态调度...

于 2013-02-05T17:41:55.967 回答
1

A 行和 B 行中多余的措辞对生成的代码完全没有影响。我不知道谁推荐这个(我从未见过),但在实践中,它唯一可能产生影响的时候是函数不是虚拟的。只需编写foo_impl(),并完成它。

如果编译器知道派生类型,有一种方法可以避免调用虚函数。我已经看到它用于类似向量的类(其中有不同的实现,例如向量的正常、稀疏等):

template <typename T>
class Base
{
private:
    virtual T& getValue( int index ) = 0;
public:
    T& operator[]( int index ) { return getValue( index ); }
};

template <typename T>
class Derived : public Base<T>
{
private:
    virtual T& getValue( int index )
    {
        return operator[]( index );
    }
public:
    T& operator[]( index )
    {
        //  find element and return it.
    }
};

这里的想法是您通常只通过对基类的引用来工作,但如果性能成为问题,因为您[]在紧密循环中使用,您可以dynamic_cast在循环之前访问派生类,并[]在派生类上使用。

于 2013-02-05T16:55:50.053 回答