简体   繁体   English

为什么 C++ 支持带有实现的纯虚函数?

[英]Why does C++ support pure virtual functions with an implementation?

I did a simple test today:今天做了一个简单的测试:

struct C{virtual void f()=0;};
void C::f(){printf("weird\n");}

The program is OK, but is weird to me, when we use =0 it means the function body should be defined in the inherited classes, but it seems I can still give it implementation function.程序没问题,但对我来说很奇怪,当我们使用=0时,这意味着函数体应该在继承的类中定义,但似乎我仍然可以给它实现函数。

I tried both GCC and VC, both OK.我尝试了 GCC 和 VC,都可以。 So it seems to me this should be part of C++ standard.所以在我看来,这应该是 C++ 标准的一部分。

But why this is not a syntax error?但为什么这不是语法错误?

A reason I could think of is like C# having both 'interface' and 'abstract' keywords, interface can't have an implementation, while abstract could have some implementations.我能想到的一个原因就像 C# 同时具有“接口”和“抽象”关键字,接口不能有实现,而抽象可以有一些实现。

Is this the case for my confusion, that C++ should support such a kind of weird syntax?这是否让我感到困惑,C++ 应该支持这种奇怪的语法?

C++ Supports pure virtual functions with an implementation so class designers can force derived classes to override the function to add specific details , but still provide a useful default implementation that they can use as a common base. C++ 支持带有实现的纯虚函数,因此类设计者可以强制派生类覆盖该函数以添加特定细节,但仍提供有用的默认实现,它们可以用作公共基础。

Classic example:经典例子:

class PersonBase
{
private:
    string name;
public:
    PersonBase(string nameIn) : name(nameIn) {} 

    virtual void printDetails() = 0
    {
        std::cout << "Person name " << name << endl;
    }
};

class Student : public PersonBase
{
private:
    int studentId;
public: 
    Student(string nameIn, int idIn) : PersonBase(nameIn), studentId(idIn) {  }
    virtual void printDetails()
    {
        PersonBase::printDetails(); // call base class function to prevent duplication
        std::cout << "StudentID " << studentId << endl;
    }
};

Others mentioned language consistency with the destructor, so I'll go for a software engineering stand-point:其他人提到了与析构函数的语言一致性,所以我将站在软件工程的角度:

It's because the class you are defining may have a valid default implementation, but calling it is risky/expansive/whatever.这是因为您正在定义的类可能具有有效的默认实现,但调用它是有风险的/扩展的/无论如何。 If you don't define it as pure virtual, derived classes will inherit that implementation implicitly.如果您不将其定义为纯虚拟,派生类将隐式继承该实现。 And may never know until run-time.并且可能直到运行时才知道。

If you define it as pure virtual, a derived class must implement the function.如果将其定义为纯虚拟,则派生类必须实现该功能。 And if it's okay with the risk/cost/whatever, it can call the default implementation statically as Base::f();如果风险/成本/无论如何都可以,它可以静态调用默认实现为Base::f();
What's important is that it's a conscious decision, and the call is explicit.重要的是这是一个有意识的决定,而且是明确的。

Basically, the best of both worlds (or the worst...).基本上,两全其美(或最坏的......)。

The derived class is required to implement the pure virtual method, the designer of the base class requires this for some reason.派生类需要实现纯虚方法,基类的设计者出于某种原因需要这个。 And the base class also provides a default implementation of this method that, if the derived class desires or requires it, can be used.并且基类还提供了此方法的默认实现,如果派生类需要或需要它,可以使用它。

So some sample code could look like;所以一些示例代码可能看起来像;

class Base {
public:
  virtual int f() = 0;
};
int Base::f() {
  return 42;
}

class Derived : public Base {
public:
  int f() override {
    return Base::f() * 2;
  }
};

So what is a common use case...那么什么是常见的用例...

A common use case for this technique is related to the destructor - basically the designer of the base class desires that it is an abstract class, but none of the methods make much sense as being pure virtual functions.这种技术的一个常见用例与析构函数有关——基本上,基类的设计者希望它是一个抽象类,但没有一个方法作为纯虚函数有多大意义。 The destructor is a feasible candidate.析构函数是一个可行的候选者。

class Base {
public:
  ~Base() = 0;
};
Base::~Base() { /* destruction... */ }

A pure virtual function must be overriden in subclasses.必须在子类中重写纯虚函数。 However, you can provide a default-implementation, that will work for sub-classes, but might not be optimal.但是,您可以提供一个默认实现,它适用于子类,但可能不是最佳的。

A constructed use case is for abstract shapes, eg构造的用例用于抽象形状,例如

class Shape {
public:
    virtual Shape() {}

    virtual bool contains(int x, int y) const = 0;
    virtual int width() const = 0;
    virtual int height() const = 0;
    virtual int area() const = 0;
}

int Shape::area() const {
    int a = 0;
    for (int x = 0; x < width(); ++x) {
        for (int y = 0; y < height(); ++y) {
            if (contains(x,y)) a++;
        }
    }
    return a;
}

The area method will work for any shape, but is highly inefficient.面积法适用于任何形状,但效率极低。 Subclassers are encouraged to provide a suitable implementation, but if there is none available, they still can explicitely call the base class's method鼓励子类提供合适的实现,但如果没有可用的实现,它们仍然可以显式调用基类的方法

Please note that you cannot instantiate an object with pure virtual methods.请注意,您不能使用纯虚方法实例化对象。

Try to instantiate:尝试实例化:

C c;

with VC2015, there is an error as expected:使用 VC2015,出现预期的错误:

1>f:\dev\src\consoleapplication1\consoleapplication1.cpp(12): error C2259: 'C': cannot instantiate abstract class 
1>f:\dev\src\consoleapplication1\consoleapplication1.cpp(12): note: due to following members: 
1>f:\dev\src\consoleapplication1\consoleapplication1.cpp(12): note: 'void C::f(void)': is abstract 
1>f:\dev\src\consoleapplication1\consoleapplication1.cpp(6): note: see declaration of 'C::f'

To answer your question: The mechanisms only declares the function to be pure virtual, but there is still the virtual function table and the baseclass.回答您的问题:机制仅将函数声明为纯虚拟,但仍然存在虚拟函数表和基类。 It will avoid you instanciate Baseclass (C), but does not avoid using it:它将避免您实例化 Baseclass (C),但不会避免使用它:

struct D : public C { virtual void f(); }; 
void D::f() { printf("Baseclass C::f(): "); C::f(); }
...
D d; 
d.f();

Pure virtual means "child must override".纯虚拟意味着“孩子必须覆盖”。

So:所以:

struct A{ virtual void foo(){}; };
struct B:A{ virtual void foo()=0; };
struct C:B{ virtual void foo(){}; };
struct D:C{ virtual void foo()=0; };
void D::foo(){};
struct E:D{ virtual void foo(){D::foo();}; };

A has a virtual foo. A 有一个虚拟 foo。

B makes it abstract. B 使其抽象化。 Before making an instance, derived types must implement it now.在创建实例之前,派生类型必须立即实现它。

C implements it. C 实现它。

D makes it abstract, and adds an imllementation. D 使其抽象,并添加了一个实施。

E implements it by calling D's implementation. E 通过调用 D 的实现来实现它。

A, C and E can have instances created. A、C 和 E 可以创建实例。 B and D cannot. B 和 D 不能。

The technique of abstract with implementation can be used to provide a partial or inefficient implementation that derived types can call explicitly when they want to use it, but do not get "by default" because that would be ill advised.带有实现的抽象技术可用于提供部分或低效的实现,派生类型可以在它们想要使用它时显式调用,但不要“默认”,因为这是不明智的。

Another intersting use case is where the parent interface is in flux, and tue code base is large.另一个有趣的用例是父接口不断变化,并且代码库很大。 It has a fully functional implementation.它有一个功能齐全的实现。 Children who use the default must repeat the signature and forward explicitly to it.使用默认值的孩子必须重复签名并明确转发给它。 Those that want to override simply override.那些想要覆盖的只是覆盖。

When the base class sigrnature changes, the code will fail to compile unless every child either explicitly calls the default or properly overrides.当基类签名更改时,代码将无法编译,除非每个孩子都显式调用默认值或正确覆盖。 Prior to the override keyword this was the only way to ensure you did not accidentally create a new virtual function instead of overriding a parent, and it remains the only way where the policy is enforced in the parent type.override关键字之前,这是确保您不会意外创建新虚函数而不是覆盖父对象的唯一方法,并且它仍然是在父类型中强制执行策略的唯一方法。

The destructor must be defined, even if it is pure virtual.必须定义析构函数,即使它是纯虚拟的。 If you don't define the destructor the compiler will generate one.如果你没有定义析构函数,编译器会生成一个。

Edit: you can't leave destructor declared without define, will cause link error.编辑:你不能在没有定义的情况下声明析构函数,会导致链接错误。

You can anyway call the body of the function from derived classes.无论如何,您都可以从派生类中调用函数体。 You can implement the body of a pure virtual function to provide a default behavior, and at the same time you want that the designer of the derived class use that function explicitly.您可以实现纯虚函数的主体以提供默认行为,同时您希望派生类的设计者显式使用该函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM