具有相同名称的纯虚函数的不同实现[重复]
Posted
技术标签:
【中文标题】具有相同名称的纯虚函数的不同实现[重复]【英文标题】:Distinct implementations for pure virtual functions with same name [duplicate] 【发布时间】:2011-10-28 06:17:45 【问题描述】:可能重复:Inherit interfaces which share a method name
我有两个基类I1
和I2
,带有纯虚函数void R() = 0;
。我希望派生类IImpl
继承自I1
和I2
,并为I1::R()
和I2::R()
提供不同的实现。
下面的代码在 MS VS 2005 和 2010 中编译和工作。我在禁用语言扩展和警告级别 4 的情况下编译。没有警告和错误。
我在 gcc 4.2 中尝试了相同的代码。它不编译。 GCC 报错:
error: cannot define member function 'I1::R' within 'IImpl'
我的问题是:
-
为什么该代码在 MS VS 中有效,为什么在 gcc 中无效?
代码是标准 C++ 吗?
什么是正确的实现方式,所以它是标准的 C++ 并且在 VS 和 gcc 上编译?
谢谢!
#include <stdio.h>
class I1
public:
virtual void R() = 0;
virtual ~I1()
;
class I2
public:
virtual void R() = 0;
virtual ~I2()
;
class IImpl: public I1, public I2
public:
virtual void I1::R()
printf("I1::R()\r\n");
virtual void I2::R()
printf("I2::R()\r\n");
;
int main(int argc, char* argv[])
IImpl impl;
I1 *p1 = &impl;
I2 *p2 = &impl;
p1->R();
p2->R();
return 0;
【问题讨论】:
+1 为清楚起见。我很惊讶它完全可以编译。对我来说它看起来不标准。 @BjörnPollex,由于问题 1. 和 2,我认为这不是重复的。 Visual C++ 的新语法可以找到on MSDN 【参考方案1】:此代码是非标准的,根据 8.3/1
除了在其类之外定义成员函数 (9.3) 或静态数据成员 (9.4) 之外,不得限定 declarator-id,...
所以在类中声明/定义成员函数名时,你不能限定它们。这就是为什么它不被 gcc 编译。
MSVC 似乎有一个允许此类代码的非标准扩展。我猜它是为 C++ 的托管扩展完成的,这正是在那里完成显式接口实现的方式,虽然它很久以前就被弃用了,但似乎仍然支持语法。
Björn Pollex 提供的link 描述了实现它的正确方法。
【讨论】:
【参考方案2】:您只能在IImpl
中实现一次void R()
。
【讨论】:
【参考方案3】:IImpl impl;
I1 *p1 = &impl;
I2 *p2 = &impl;
p1->R();
p2->R();
这对我来说没有意义。 R()
是一个virtual 方法,所以无论你在I1*
还是I2*
上调用它都没有关系,只要真实对象是IImpl
。
此外,IImpl
有两个相似的R()
,你希望哪一个被称为impl.R()
?
我对引用它的标准不够熟悉,但我确定你在做的是一个错误。
如果我错了,请纠正我。
【讨论】:
虽然它是一个非标准代码,但它仍然有意义。它就像 .NET 中的显式接口实现一样工作。从内部看,I1 和 I2 都有指向虚函数表的指针,不难想象这些指针指向不同的表,从而导致调用不同的方法。这样做仍然是 C++ 中的错误。 语法不标准,但基本概念完美。 请阅读我的answer。【参考方案4】:@紫罗兰长颈鹿
这是对紫罗兰色长颈鹿的回答。也消除了对后期绑定和MI的一些误解,比Java继承更灵活。
因此,只要真实对象是 IImpl,在 I1* 或 I2* 上调用它都没有关系。
// interfaces
class I1
public:
virtual void f () = 0;
;
class I2
public:
virtual void f () = 0;
;
// concrete implementations
class C1 : public I1
public:
virtual void f () cout << "C1::f()\n";
;
class C2 : public I2
public:
virtual void f () cout << "C2::f()\n";
;
// putting both together
class D : public C1, public C2
;
template <class I>
void f (I &i)
i.f(); // virtual call to I::f()
int main()
D d;
f<I1> (d); // virtual call to I1::f(): prints C1::f()
f<I2> (d); // virtual call to I2::f(): prints C2::f()
对成员函数的调用总是静态绑定到一个函数声明(此处为I1::f()
和I2::f()
)。在运行时,调用的函数(被称为“后期绑定”的进程)是这个虚函数(这里是C1::f()
和C2::f()
)的派生度最高的覆盖器(技术术语是“final”) .
在任何类中,每个纯虚函数必须最多有一个最终覆盖。在非抽象类中,每个虚函数都必须只有一个最终覆盖器。
现在您看到I1::f()
和I2::f()
是不同的功能,而且它们完全不相关;但是两者都具有相同的非限定名称和相同的参数,因此无法通过重载来区分它们。如果使用限定名(d.I1::f()
和d.I2::f()
),调用将绑定到正确的声明,但虚拟性也将被禁止(在这种情况下,纯虚函数的定义必须存在并将被调用)。所以调用这两个函数的唯一方法就是我在这里所做的:首先创建一个I1
/I2
类型的左值,然后才进行不合格的调用.f()
。
旁注:这种不同的覆盖在 Java 中是不可能的,因为 MI 受到严格限制,并且覆盖遵循不同的规则。
另外,IImpl 有两个相似的 R(),你希望哪一个被调用为 impl.R()?
您希望在d.f()
中调用哪个f()
?
【讨论】:
首先,感谢您的详细评论,真的很有教育意义。很遗憾我不能为这篇文章投超过一票。模板技巧非常简洁。现在,至于调用d.f()
:我预计会得到编译错误,一些关于模糊调用的东西。我很惊讶错误是'f': identifier not found
(MSVC 2010)。怎么样?
错误信息不是很有帮助。 (我已经看到许多来自许多不同编译器的非常奇怪的消息的示例,但 MSVC 是最奇怪的。)“找不到标识符”错误可能与这里的问题不是重载无法选择一个最佳匹配的事实有关,因为重载甚至不会发生:这里 名称查找无法选择一组候选重载函数。这是因为重载可以与不同范围内的函数一起使用,但如果它们是普通成员函数则不能。这里名称查找在不同的类中找到两个 f()
成员。以上是关于具有相同名称的纯虚函数的不同实现[重复]的主要内容,如果未能解决你的问题,请参考以下文章