具有相同名称的纯虚拟函数的不同实现

Distinct implementations for pure virtual functions with same name

本文关键字:实现 虚拟 函数      更新时间:2023-10-16

可能重复:
继承共享方法名的接口

我有两个基类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'

我的问题是:

  1. 为什么该代码在MSVS中有效,为什么在gcc中不起作用
  2. 该代码是标准C++吗
  3. 实现它的正确方法是什么,所以它是一个标准的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()rn");
    }
    virtual void I2::R()
    {
        printf("I2::R()rn");
    }
};
int main(int argc, char* argv[])
{
    IImpl impl;
    I1 *p1 = &impl;
    I2 *p2 = &impl;
    p1->R();
    p2->R();
    return 0;
}

根据8.3/1 ,此代码是非标准的

声明符id不应被限定,除非定义了其类之外的成员函数(9.3(或静态数据成员(9.4(,。。。

因此,在类中声明/定义成员函数名时,不能限定它们。这就是为什么它不是由gcc编译的。

MSVC似乎有一个允许此类代码的非标准扩展。我想这是为C++托管扩展所做的,这正是显式接口实现的方式,尽管它在很久以前就被弃用了,但语法似乎仍然受到支持。

Björn Pollex提供的链接描述了实现它的正确方式。

IImpl中只能实现一次void R()

IImpl impl;
I1 *p1 = &impl;
I2 *p2 = &impl;
p1->R();
p2->R();

这对我来说没有意义。R()是一个虚拟方法,所以只要实际对象是IImpl,在I1*还是I2*上调用它都无关紧要。此外,IImpl有两个类似的R(),您希望impl.R()调用哪一个?我对标准还不够熟悉,不能引用它,但我确信你所做的是一个错误。如果我错了,请纠正我。

@紫罗兰长颈鹿

这是对Violet Giraffe的回答。它还消除了对后期绑定和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()