使用模板调用正确的派生类方法而不使用虚拟?

Calling proper derived class' method using templates without using virtual?

本文关键字:类方法 虚拟 派生 调用      更新时间:2023-10-16

我有类似附加的东西。 我基本上有一个 Doer 类,我想从它的成员调用 Func(),而不使用虚拟或尽可能少的代码重复。 此外,提升也不是一种选择。 我知道这个例子可能不是那么清楚,但我希望你明白这个想法。 乙

class Base { // a bunch of shared base functionality. Cannot be instantiated by itself  }
class D1 : public Base
{
   void Func();
}
class D2 : public Base
{
   void Func();
}
//----
class Doer
{
   Doer(Base* b) : base(b) { } 
   void DoIt()
   {
      base->Func();
   }
   Base* base;
}

好吧,你可以Doer模板化:

template<class T>
class Doer
{
public:
   Doer(T* b) : base(b) { } 
   void DoIt()
   {
      base->Func();
   }
private:
   T* base;
};

但为此,我只想在Base中添加一个virtual void Func()

请注意,无论哪种情况,您都可能希望公开Func:-)

这种方法怎么样:

class Base { // a bunch of shared base functionality. Cannot be instantiated by itself  
   ~Base() { //stuff }
   void Func();
}
class D1 : public Base
{
   void Func();
}
class D2 : public Base
{
   void Func();
}
//----
class Doer
{
   Doer(Base* b) : base(b) { } 
   void DoIt()
   {
      base->Func();
   }
   Base* base;
}

由于 Func() 不是虚拟的并且被子项重载,因此不应该有 vtable 或任何产生的性能损失,对吗?

此外,析构函数需要在基类上调用,但声明它是虚拟的会强加一个 vtable?

谁能澄清一下?

谢谢

你可以使用mixins!它们有利于优化(大量内联机会,没有虚拟方法调用),但有时有点难以推理。 下面是使用 mixins 实现的示例:

template<class Base> class Doer : Base {
public:
    Doer() {}
    void DoIt() {
        this->Func();
    }
};
class D1 {
public:
    void Func() {
        cout<<"Hello from D1"<<endl;
    }
};
class D2 {
public:
    void Func() {
        cout<<"Hello from D2"<<endl;
    }
};

使用此方法略有不同,因为 Doer 与基类实例相同。 以下程序:

Doer<D1> *d1 = new Doer<D1>();
Doer<D2> *d2 = new Doer<D2>();
d1->DoIt();
d2->DoIt();

生成输出:

来自D1的你好

来自D2的你好

这有一个明显的缺点,即 D1 和 D2 不会强制实现"Func"方法。 如果你忘记了它,你会收到一个非常方便的C++模板实例化错误,而不是"找不到方法"。 如果您要经常使用模板,Clang 是一个不错的选择,因为与 g++ 相比,您会收到更有用的编译器错误。 另一个缺点是构造函数:Doer 定义默认构造函数,但不公开 D1 的构造函数。C++11 允许构造函数继承,因此可以使用编译器标志避免此问题。

实际上你

不需要参数化整个Doer类。这将正常工作(接近ccurtsinger的建议):

class Base {
public:
    void Func() {};
};
class B1 {
public:
    void Func() { cout << "in B1::Func" << endl;}
};
class B2 {
public:
    void Func() { cout << "in B2::Func" << endl;}
};
class Doer {
public:
    template <class B> void Do(B *pb) {pb->Func();}
};
int main() {
    B1 b1;
    B2 b2;
    Doer d;
    d.Do<B1>(&b1);
    d.Do<B2>(&b2);
    return 0;
}

但实际上有一个更大的问题:从你说你最终使用的代码来看,似乎在编译时你确切地知道你正在处理哪些派生类对象,所以代码如下:

for(auto i = begin(B1_container); i != end(B1_container); ++i) {
    i->Func();
}
for(auto j = begin(B2_container); j != end(B2_container); ++j) {
    j->Func();
}

应该做这个伎俩。

我要说的是 - 你要么提前知道你在这里使用 B1-s,在那里使用 B2-s,并且 Func() 调用没有额外的成本,要么你不知道你接下来要处理哪一个,然后你需要检查它是某种类型的特征或其他任何东西的动态类型,这是一个"如果",因此分支,因此错误预测和开销。请注意,我没有添加函数调用的成本,无论如何,这两种情况都存在。