具有相同名称的纯虚函数的不同实现[重复]

Posted

技术标签:

【中文标题】具有相同名称的纯虚函数的不同实现[重复]【英文标题】:Distinct implementations for pure virtual functions with same name [duplicate] 【发布时间】:2011-10-28 06:17:45 【问题描述】:

可能重复:Inherit interfaces which share a method name

我有两个基类I1I2,带有纯虚函数void R() = 0;。我希望派生类IImpl 继承自I1I2,并为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() 成员。

以上是关于具有相同名称的纯虚函数的不同实现[重复]的主要内容,如果未能解决你的问题,请参考以下文章

C++的纯虚函数实现和头文件

继承的纯虚函数

C++11接口纯虚析构函数

C++中的纯虚析构函数

抽象类中的纯虚函数,返回类型为基/派生类型

为啥我们需要 C++ 中的纯虚析构函数?