带有实现的纯虚函数

Posted

技术标签:

【中文标题】带有实现的纯虚函数【英文标题】:Pure virtual function with implementation 【发布时间】:2011-01-06 12:58:51 【问题描述】:

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

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

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

上面的代码可以吗?

让它成为一个的目的是什么?

【问题讨论】:

【参考方案1】:

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++ 程序员,甚至是有经验的程序员都感到惊讶)。

【讨论】:

你忘了补充为什么它让程序员感到惊讶:这是因为标准禁止内联定义。纯虚方法定义必须是deported。 (在 .inl 或 .cpp 中引用常见的文件命名实践)。 所以这个调用方法和静态方法成员调用一样。 Java 中的某种类方法。 "不常用" == 不好的做法?我正在寻找完全相同的行为,试图实现 NVI。 NVI 对我来说似乎是一个很好的做法。 值得指出的是,将 A::f() 设为纯意味着 B 必须 实现 f()(否则 B 将是抽象且不可实例化的)。正如@MichaelBurr 指出的那样,为 A::f() 提供一个实现意味着 B 可以使用它来定义 f()。 IIRC,Scot Meyer 在他的一本经典著作“更有效的 C++”中有一篇关于这个问题的用例的优秀文章【参考方案2】:

需要明确的是,您误解了 what = 0;在虚函数之后的意思。

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

在实践中,当您将虚函数标记为纯 (=0) 时,提供定义几乎没有意义,因为除非有人通过 Base::Function(...) 明确地调用它,否则它永远不会被调用或者如果基类构造函数调用有问题的虚函数。

【讨论】:

这是不正确的。如果您在纯虚类的构造函数中调用该纯虚函数,则会进行纯虚调用。在这种情况下,你最好有一个实现。 @rmn,是的,关于构造函数中的虚拟调用,您是对的。我更新了答案。不过,希望每个人都知道不要这样做。 :) 事实上,从构造函数进行基本纯调用会导致实现定义的行为。在 VC++ 中,这相当于 _purecall 崩溃。 @OfekShilon 是正确的 - 我也很想将其称为未定义的行为和不良实践/代码重构的候选者(即在构造函数中调用虚拟方法)。我想这与虚拟表的一致性有关,它可能没有准备好路由到正确实现的主体。 在构造函数和析构函数中,虚函数不是虚函数。【参考方案3】:

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

【讨论】:

如果有默认实现,我为什么要强制?这听起来像普通的虚拟功能。如果它只是一个普通的虚函数,我可以覆盖,如果我没有,那么将提供默认实现(base 的实现)。【参考方案4】:

如果您有应该由派生类执行的代码,但您不希望它被直接执行——并且您想强制它被覆盖。

您的代码是正确的,尽管总而言之,这不是一个经常使用的功能,并且通常仅在尝试定义纯虚拟析构函数时才会看到——在这种情况下,您必须提供一个实现.有趣的是,一旦你从那个类派生,你就不需要重写析构函数了。

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

以下代码出奇地正确:

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

Base::~Base() 

class Derived : public Base ;

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

【讨论】:

基类析构函数总是被调用,不管是不是虚拟的,也不管是不是纯的;对于其他函数,您不能保证覆盖的虚函数将调用基类实现,无论基类版本是否纯。 那个代码是错误的。由于语言的语法怪癖,您必须在类定义之外定义 dtor。 @Roger :谢谢,这实际上帮助了我——这是我一直在使用的代码,它在 MSVC 下编译得很好,但我想它不会是可移植的。【参考方案5】:

你必须给一个纯虚拟析构函数一个主体,例如:)

阅读:http://cplusplus.co.il/2009/08/22/pure-virtual-destructor/

(Link broken, use archive)

【讨论】:

【参考方案6】:

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

Scott Meyers 在Effective C++ (2nd Edition) Item #36 中讨论了这个问题 区分接口继承和实现继承。最新版本中的项目编号可能已更改。

【讨论】:

【参考方案7】:

带有或不带有主体的纯虚函数仅仅意味着派生类型必须提供自己的实现。

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

【讨论】:

【参考方案8】:

'虚 void 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++有很多漏洞。

希望我能帮上忙:-)

【讨论】:

它不需要在所有派生类中实现,但它必须在您打算实例化的所有派生类中都有实现。在您的示例中,您不能实例化 C 类型的对象。您可以实例化D 类型的对象,因为它从B 获取foo 的实现。【参考方案9】:

如果我问你什么是动物的声音,正确的回答是问哪个动物,这正是纯虚函数的目的,或者抽象函数是当你不能在基类中为你的函数提供实现时(动物),但每种动物都有自己的声音。

class Animal

   public:
       virtual void sound() = 0;


class Dog : public Animal

   public:
       void sound()
       
           std::cout << "Meo Meo";
       

【讨论】:

【参考方案10】:

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

class Foo

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

Foo::~Foo()


这种技术使Foo 类抽象化,因此无法直接实例化该类。同时,您还没有添加额外的纯虚方法来使Foo 类抽象。

【讨论】:

以上是关于带有实现的纯虚函数的主要内容,如果未能解决你的问题,请参考以下文章

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

继承的纯虚函数

在这种情况下,我可以避免使用身体的纯虚函数吗

C++中的纯虚析构函数

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

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