【问题标题】:How can I partially overload a virtual function in a C++ template subclass?如何在 C++ 模板子类中部分重载虚函数?
【发布时间】:2016-02-08 02:30:27
【问题描述】:

我正在尝试创建一个非模板基类来创建一个接口,通过该接口我可以与派生的模板类进行交互。我希望使用部分虚函数重载,但有些东西不起作用,我不知道为什么。 谁能解释为什么我下面的代码返回:

B match = False
D match = True
E match = False

而不是 B、D 返回 True 和 E 返回 False?我原以为派生类中的重载运算符会获取指向 int '&i' 的指针并被调用,但事实并非如此。

需要明确的是,我不是要 - 覆盖 - 匹配的基本版本,我是要 - 重载 - 它,特别是希望它有一个不同的,在这种情况下更专业的接口,而不是Base,在使用其函数签名时接管。

我还试图避免必须为我可能实例化的派生模板的每种风格扩展基类。

更陌生,我 - 可能 - 只是在这里发疯,但我发誓这在不久前的某个时候对我有用! Fwiw 我在 OSX 10.5 上,使用 llvm 7.0.0 和 clang 700.1.76。这可能是编译器的特性吗?

虽然我在这里尝试(未成功)使用部分重载,但我真的很愿意接受任何方法来解决通过其参数类型选择模板实例函数的问题,而不增加类,if-thens/case或向基类添加特定的专业化。如果您有其他类似功能的方法,我很乐意听到。

感谢您提供的任何见解!

#include <stdio.h>

class Base
{
public:
  Base() {}
  virtual ~Base(){}

  virtual bool match( const void *data ) const { return false; }
};

template <class Type>
class Derived: public Base
{
public:
  Derived():Base() {}
  ~Derived() override{}

  virtual bool match( const Type *data ) const { return true; }
};

int main(int argc, char **argv)
{
  Derived<int>   *d = new Derived<int>();
  Derived<float> *e = new Derived<float>();
  Base *b           = d;

  int i;
  printf("B match = %s\n",b->match(&i)?"True":"False");
  printf("D match = %s\n",d->match(&i)?"True":"False");
  printf("E match = %s\n",e->match(&i)?"True":"False");

}

【问题讨论】:

  • πάντα ῥεῖ,好点子!我忘了我可以编辑问题! :) 已修复。
  • 你的虚函数不纯。看看这是否给你一个合理的解决方案:stackoverflow.com/questions/2919584/…
  • @o_weisman,感谢您的评论。使我的 Base 函数成为纯虚拟函数似乎并不能解决这个设计问题。您提到的问题是值得深思的问题,但如果不为我可能使用的每个派生类型增加特定的子类,我无法弄清楚如何应用它。换句话说,如果模板定义中没有内置的实例选择机制,则必须编写和实例化许多特定的类。不过还是谢谢你,感谢你的帮助。
  • 做一些搜索我想你想要的是函数模板,这是一个例子stackoverflow.com/a/972170/2899478
  • @o_weisman 谢谢!我能够使用功能模板想出一个解决方案......我发布了一个基于它们的正确功能的答案。感谢您引导我朝着更有成效的方向前进!

标签: c++ templates overloading


【解决方案1】:

如果您要手动创建 Derived&lt;int&gt; 作为一个类,它的成员函数将是:

virtual bool match( const int *data ) const { return true; }

它不会覆盖基类。因此,当你用基类指针调用函数时,它执行基类实现,当你用派生类指针调用它时,它执行派生类实现。

如果您使用 override 关键字,您将能够在编译时发现问题。

template <class Type>
class Derived: public Base
{
public:
  Derived():Base() {}
  ~Derived() override{}

  virtual bool match( const Type *data ) const override { return true; }
};

您应该会看到与该更改相关的编译时错误。

http://ideone.com/8rBQ6B查看编译器错误。

更新,以回应 OP 的评论

如果您不想覆盖:

  1. 不要在成员函数声明中使用virtual
  2. 通过使用将基类函数带入派生类的作用域

    using Base::match
    

    这是怎么做的:

    template <class Type>
    class Derived: public Base
    {
    public:
      Derived():Base() {}
      ~Derived() override{}
    
      using Base::match;
      bool match( const Type *data ) const { return true; }
    };
    

【讨论】:

  • 使用 override 关键字的好电话。
  • 感谢您的想法 R Shau。需要明确的是,我不是要覆盖 Base 版本的 match,我是要重载它,特别是希望它有一个不同的,在这种情况下更专业的接口,而不是 Base 中的接口,它在使用其函数签名时接管。
  • @RSahu...感谢您的编辑...哦,我希望这对我有用!这似乎是一个优雅而简单的更改,但不幸的是它对我产生了相同的结果。
  • @NathanOliver,我不认为你想问这个问题意味着什么。我想你的意思是问一个不同的问题,其答案是:不,你将无法从指针调用 Derived 成员函数。
  • @RSahu 不,抱歉。我可能应该注销并休息一下。不过谢谢。
【解决方案2】:

这里的问题是Derived::match 不匹配Base::match。虚函数的函数签名必须相同才能使机制起作用。因此,使用您的代码 Derived::match 重载而不是覆盖它。

如果我们改变

virtual bool match( const Type *data ) const { return true; }

virtual bool match( const void *data ) const { return true; }

然后我们得到

B match = True
D match = True

Live Example

【讨论】:

  • 感谢您的想法内森。需要明确的是,我并不是要覆盖 Base 版本的 match,而是要重载它,特别是希望它具有与 Base 中的接口不同的,在这种情况下更专业的接口,后者在使用其函数签名时接管。
  • @Dave 这样做会破坏虚拟机制,如您所见。如果函数是重载,那么当您从 Base* 调用函数时,只会考虑 Base* 函数。
  • 内森,是啊..它肯定坏了! :) 你能想到我可以从它的参数类型中选择派生模板类函数的任何其他方式吗?
  • @Dave 除了将其设为void*,然后在函数中转换为模板类型之外,我不知道还有什么方法。你可能想看看boost::variantboost::any
  • 我会看看那些。知道我是否可以在 Base 中放置一个函数模板进行匹配,然后使用 Derived 在 Derived 类中使用的相同类型对其进行专门化?
【解决方案3】:

因为函数的签名不匹配:

class Base 你有

virtual bool match( const void *data ) const { return false; }

注意const void * 参数类型

然后在Derived 你有

virtual bool match( const Type *data ) const { return true; }

这里的Typeint(来自mainDerived&lt;int&gt; *d

只需在 Base 中添加一个带有 const int* data 签名的虚拟,就可以开始了。

是的,这确实意味着在Base 中,您必须为您希望与Derived 一起使用的每个Type 添加match 的重载。

【讨论】:

  • 感谢您的回答 Gread.And.Powerful.Oz!我同意这可行,但它还需要我为我打算与系统一起使用的每种类型修改具有特定函数重载的基类。这是我希望避免的。如果这个不能,你能想出另一种机制来实现这一点吗?
  • @Dave 我唯一能想到的就是让bool Derived&lt;Type&gt;::match(const Type* data) { return match((void*)data); } 函数调用另一个virtual bool Derived&lt;Type&gt;::match(const void* data) override { return true; }。第一个函数是重载,第二个是覆盖。或者,反过来调用。
  • 再次感谢。无。当我这样做时,我得到误报: Derived( )->match(&i) 返回 true,当我希望它返回 false 时(因为 &i 是 int* 而不是 float* )。
  • @Dave,您需要将match(void *) 中的Derived 函数设为私有以避免这种情况。
  • 哦,鬼鬼祟祟! :) 该修改确实适用于 B 和 D,但对于反例 E 失败(请参阅上面的修改问题),我收到以下编译器错误“匹配”是“派生的私有成员”。我猜使用 match(const void *) private,没有更多的“catch all”函数来为不匹配的类型返回 false。还是谢谢你!我很欣赏你的创造力。
【解决方案4】:

这不起作用的原因是派生类实际上并没有覆盖基类中的虚函数。为了覆盖基类中的虚函数,派生类必须具有完全相同相同的签名(请参阅下面的注释了解例外情况)。在这种情况下,签名是不同的,因为基类中的虚函数采用void *,而派生类中的函数采用type *

让我们做一个小改动,将override 指令添加到派生类的函数签名中:

#include <stdio.h>

class Base
{
public:
  Base() {}
  virtual ~Base(){}

  virtual bool match( const void *data ) const { return false; }
};

template <class Type>
class Derived: public Base
{
public:
  Derived():Base() {}
  ~Derived() override{}

  virtual bool match( const Type *data ) const override { return true; }
};

int main(int argc, char **argv)
{
  Derived<int> *d = new Derived<int>();
  Base *b         = d;

  int i;
  printf("B match = %s\n",b->match(&i)?"True":"False");
  printf("D match = %s\n",d->match(&i)?"True":"False");

}

当我们现在编译时会发生以下情况:

main.cpp: In instantiation of 'class Derived<int>':
main.cpp:24:38:   required from here
main.cpp:19:16: error: 'bool Derived<Type>::match(const Type*) const [with Type = int]' marked 'override', but does not override
   virtual bool match( const Type *data ) const override { return true; }

假设我们使用的是 C++11,最好使用override 来确保我们确实覆盖了基类虚函数。

上面的注意事项:有一种叫做 covariant return type 的东西,其中覆盖不必具有相同的签名,但这超出了问题的范围。

【讨论】:

  • 感谢您的回答。我看了一下协变返回类型——很有趣。不过,我想不出在上述情况下如何应用它,因为我试图根据成员函数的参数类型选择模板类方法。不过好主意,我学到了一些新东西! :)
  • 是的,抱歉,我并不是要暗示协变返回类型解决了这个特殊问题。应该更清楚这一点。我担心如果我不提它,有人可能会反对我在那个(多余的)理由上的回应。
【解决方案5】:

感谢 o_weisman 在原始问题下的评论,我能够使用函数模板找到可行的解决方案(见下文)。诚然,在我的解决方案的当前形式中,我利用了 Derived 的每个类型实例都是单例的限制。这适用于我的特定设计,但可以根据需要扩展行为。允许 Derived 的多个实例的一种可能性可能是检查 Base::match 中的“this”指针是否在所有实例的集合中(在构造/销毁时更新的静态集合变量中跟踪),而不是反对一个单例实例。无论如何,我希望这对可能面临类似设计挑战的人有所帮助。

#include <stdio.h>
#include <assert.h>

template <class Type> class Derived;
class Base
{
public:
  Base() {}
  virtual ~Base(){}

  template <class Type>
  bool match( const Type *data ) const { return (Derived<Type>::instance() == this); }

};

template <class Type>
class Derived: public Base
{
public:
  Derived(): Base() { assert(!ourInstance); ourInstance = this; }
  ~Derived() override{}

  static const Base *instance() { return ourInstance; }

protected:
  static const Derived<Type> *ourInstance;

};

template <class Type>
const Derived<Type> *Derived<Type>::ourInstance = NULL;

int main(int argc, char **argv)
{
  Derived<int>   *d = new Derived<int>();
  Derived<float> *e = new Derived<float>();
  Base *b           = d;

  int i;
  printf("B match = %s\n",b->match(&i)?"True":"False");
  printf("D match = %s\n",d->match(&i)?"True":"False");
  printf("E match = %s\n",e->match(&i)?"True":"False");

}

这会产生以下结果:

B match = True
D match = True
E match = False

【讨论】:

    猜你喜欢
    • 2013-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-07
    • 2016-10-03
    • 2021-03-19
    • 1970-01-01
    • 2021-11-08
    相关资源
    最近更新 更多