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

时间:2011-10-28 06:17:46

标签: c++ visual-c++ gcc

  

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

我有两个带有纯虚函数I1的基类I2void 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. 为什么代码在MS VS中起作用?为什么它在gcc中不起作用?
  2. 代码是标准C ++吗?
  3. 实现它的正确方法是什么,所以它是标准的C ++并在VS和gcc上编译?
  4. 谢谢!

    #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;
    }
    

4 个答案:

答案 0 :(得分:6)

根据8.3 / 1

,此代码是非标准的
  

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

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

MSVC似乎有一个允许此类代码的非标准扩展。我想它是针对C ++的Managed Extensions完成的,这正是在那里进行显式接口实现的方式,虽然它很久以前就被弃用了但似乎仍然支持语法。

在BjörnPollex提供的link中描述了实施它的正确方法。

答案 1 :(得分:1)

您只能在void R()中实施一次IImpl

答案 2 :(得分:1)

IImpl impl;

I1 *p1 = &impl;
I2 *p2 = &impl;

p1->R();
p2->R();

这对我没有意义。 R()虚拟方法,因此只要真实对象为{{1},您是在I1*还是I2*上调用它并不重要}。 此外,IImpl有两个相似的IImpl,你期望为R()调用一个impl.R()? 我不太熟悉这个标准引用它,但我确定你所做的是一个错误。 如果我错了,请纠正我。

答案 3 :(得分:1)

@Violet Giraffe

这是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())。在运行时,调用的函数(通过名为&#34;过程绑定&#34;的过程)是此虚函数的最多派生覆盖(技术术语是&#34; final&#34;) (此处C1::f()C2::f())。

在任何课程中,每个虚拟函数必须有最多一个最终覆盖。在非抽象类中,每个虚函数必须有恰好一个最终覆盖。

现在你看到I1::f()I2::f()是不同的功能,而且它们完全不相关;但两者都具有相同的非限定名称和相同的参数,因此无法通过重载来区分它们。如果使用限定名称(d.I1::f()d.I2::f()),则调用将绑定到正确的声明,但虚拟内容也将被禁止(在这种情况下,必须存在纯虚函数的定义)并将被称为)。因此,调用这两种函数的唯一方法就是我在这里所做的:首先创建一个I1 / I2类型的左值,然后才进行无限制的调用.f()

附注:在Java中这种不同的覆盖是不可能的,因为MI是严格限制的,并且覆盖遵循不同的规则。

  

此外,IImpl有两个相似的R(),你期望为impl.R()调用哪一个?

您希望在f()中调用哪个d.f()