带有实现的纯虚函数


173

我的基本理解是,没有针对纯虚函数的实现,但是,有人告诉我可能存在针对纯虚函数的实现。

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

上面的代码可以吗?

用实现使其成为纯虚拟功能的目的是什么?

Answers:


213

virtual函数必须以将直接实例化的派生类型实现,但是基本类型仍可以定义实现。派生类可以显式调用基类的实现(如果访问权限允许的话)使用全范围的名称(通过调用A::f()在你的榜样-如果A::f()publicprotected)。就像是:

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

我可以想到的用例是存在某种或多或少合理的默认行为,但是类设计器希望仅显式调用默认行为。您可能希望派生类始终执行自己的工作,但又能够调用一组通用功能。

请注意,即使该语言允许使用它,也不是我看到的常用语言(而且可以做到这一点似乎令大多数C ++程序员,甚至是经验丰富的程序员都感到惊讶)。


1
您忘了添加为什么它会让程序员感到惊讶的原因:这是因为标准禁止内联定义。纯虚拟方法定义必须为deported。(在.inl或.cpp中指的是常见的文件命名惯例)。
v.oddou

因此,此调用方法与静态方法成员调用相同。Java中的某种类方法。
桑尼·刘

2
“不常用” ==不良做法?我一直在寻找完全相同的行为,试图实现NVI。NVI对我来说似乎是一个好习惯。
萨斯基亚2014年

5
值得指出的是,使A :: f()为纯意味着B 必须实现f()(否则B将是抽象且不可实例化的)。正如@MichaelBurr指出的那样,为A :: f()提供实现意味着B 可以使用它来定义f()。
fearless_fool

2
IIRC的Scot Meyer在他的经典著作之一“更有效的C ++”中有关于该问题用例的出色文章
irsis

75

明确地说,您误解了= 0;后虚函数的意思。

= 0表示派生类必须提供实现,而不是基类不能提供实现。

实际上,当您将虚拟函数标记为纯函数(= 0)时,提供定义几乎没有意义,因为除非有人通过Base :: Function(...)明确地进行了定义,否则将永远不会调用该定义。基类构造函数将调用该虚函数。


9
这是不正确的。如果您在纯虚拟类的构造函数中调用该纯虚拟函数,则将进行纯虚拟调用。在这种情况下,最好有一个实现。
rmn 2010年

@rmn,是的,您对构造函数中的虚拟调用是正确的。我更新了答案。希望每个人都知道不这样做。:)
Terry Mahaffey 2010年

3
实际上,从构造函数进行基本的纯调用会导致实现定义的行为。在VC ++中,这相当于_purecall崩溃。
Ofek Shilon 2010年

@OfekShilon是正确的-我也很想将其称为未定义行为,并且是不良作法/代码重构的候选人(即,在构造函数内部调用虚拟方法)。我想这与虚拟表的一致性有关,后者可能没有准备好路由到正确的实现主体。
teodron

1
在构造函数和析构函数中,虚函数不是虚函数。
Jesper Juhl

20

它的优点是,它强制派生类型仍然覆盖该方法,但还提供默认或附加实现。


1
如果有默认实现,为什么要强制执行?这听起来像正常的虚拟功能。如果它只是一个普通的虚函数,我可以重写,否则,则可以提供默认的实现(基本实现)。
StackExchange123

19

如果您有应由派生类执行的代码,但又不想直接执行-则希望强制将其重写。

您的代码是正确的,尽管所有这些都不是经常使用的功能,并且通常仅在尝试定义纯虚拟析构函数时才可见-在这种情况下,您必须提供一个实现。有趣的是,一旦从该类派生,就不需要覆盖析构函数。

因此,纯虚拟函数的一种合理用法是将纯虚拟析构函数指定为“非最终”关键字。

以下代码出奇的正确:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

1
无论如何,基类析构函数总是被调用。与其他函数一起,您不能保证无论基类版本是纯净的,重写的虚函数都将调用基类实现。
CB Bailey 2010年

1
该代码是错误的。由于语言的语法问题,您必须在类定义之外定义dtor。

@Roger:谢谢,这实际上对我有所帮助-这是我一直在使用的代码,它在MSVC下可以很好地编译,但是我想它不能移植。
Kornel Kisielewicz


4

是的,这是正确的。在您的示例中,派生自A的类继承接口f()和默认实现。但是,您必须强制派生类实现方法f()(即使仅是调用A提供的默认实现)。

Scott Meyers在Effective C ++(第2版)项目#36中对此进行了讨论。区分接口的继承和实现的继承。项目编号在最新版本中可能已更改。


4

具有或不具有主体的纯虚函数仅表示派生类型必须提供其自己的实现。

如果派生类要调用基类实现,则基类中的纯虚函数体很有用。


2

'虚拟无效foo()= 0;' 语法并不意味着您不能在当前类中实现foo()。这也不意味着您必须在派生类中实现它。在给我打耳光之前,让我们观察一下钻石问题:(请注意,隐式代码)。

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

现在,对obj-> foo()的调用将导致B :: foo(),然后是C :: bar()。

您会看到...纯粹的虚拟方法不必在派生类中实现(foo()在类C中没有实现-编译器会编译)在C ++中有很多漏洞。

希望我能帮助:-)


5
它不需要在所有派生类中实现,但是必须在要实例化的所有派生类中都有一个实现。您不能C在示例中实例化类型的对象。您可以实例化类型的对象,D因为它foo从实现了它的实现B
YoungJohn

0

一个具有实现主体的纯虚拟方法的重要用例是,当您想要一个抽象类,但是在该类中没有任何合适的方法使其成为纯虚拟时。在这种情况下,您可以将类的析构函数设为纯虚函数,并为此放置所需的实现(甚至是一个空的主体)。举个例子:

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

这种技术使Foo类变得抽象,因此无法直接实例化该类。同时,您没有添加其他纯虚拟方法来使Foo类抽象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.