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

时间:2016-09-08 10:09:39

标签: c++ function virtual implementation

我今天做了一个简单的测试:

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

程序没问题,但对我来说很奇怪,当我们使用=0时,意味着函数体应该在继承的类中定义,但似乎我仍然可以给它实现函数。

  

我尝试了GCC和VC,都行。所以在我看来这应该是C ++标准的一部分。

     

但为什么这不是语法错误?

我能想到的一个原因就是C#同时具有'interface'和'abstract'关键字,接口不能有实现,而abstract可能有一些实现。

这是否是我的困惑,C ++应该支持这种奇怪的语法?

8 个答案:

答案 0 :(得分:4)

C ++支持带有实现的纯虚函数,因此类设计者可以强制派生类覆盖函数以添加特定细节,但仍然提供一个有用的默认实现,它们可以作为公共基础使用。

经典示例:

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

答案 1 :(得分:2)

基本上,两全其美(或最糟糕的......)。

派生类是实现纯虚方法所必需的,基类的设计者出于某种原因需要它。并且基类还提供了此方法的默认实现,如果派生类需要或需要它,则可以使用它。

所以一些示例代码看起来像;

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

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

那么什么是常见用例......

此技术的一个常见用例与析构函数有关 - 基本类的设计者基本上希望它是一个抽象类,但这些方法都不是纯粹的虚函数。析构函数是一个可行的候选者。

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

答案 2 :(得分:2)

其他人提到语言与析构函数的一致性,所以我会选择软件工程的立场:

这是因为您定义的类可能具有有效的默认实现,但调用它是冒险/扩展/无论如何。如果您不将其定义为纯虚拟,则派生类将隐式继承该实现。直到运行时才可能知道。

如果将其定义为纯虚拟,则派生类必须实现该函数。如果风险/成本/无论如何,它可以静态调用默认实现Base::f();
重要的是它是一个有意识的决定,并且呼叫是明确的。

答案 3 :(得分:1)

必须在子类中覆盖纯虚函数。但是,您可以提供一个默认实现,它将适用于子类,但可能不是最佳的。

构造的用例适用于抽象形状,例如

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

区域方法适用于任何形状,但效率非常低。鼓励子类提供一个合适的实现,但如果没有,它们仍然可以明确地调用基类的方法

答案 4 :(得分:1)

请注意,您不能使用纯虚方法实例化对象。

尝试实现:

C c;

使用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'

回答你的问题: 机制只声明函数是纯虚函数,但仍然有虚函数表和基类。它会避免你实现Baseclass(C),但不会避免使用它:

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

答案 5 :(得分:1)

纯虚拟意味着孩子必须覆盖&#34;。

所以:

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有一个虚拟的foo。

B使它变得抽象。在创建实例之前,派生类型必须立即实现它。

C实现它。

D使它变得抽象,并添加了一个幻化。

E通过调用D的实现来实现它。

A,C和E可以创建实例。 B和D不能。

抽象与实现的技术可用于提供部分或低效的实现,派生类型可以在需要使用它时显式调用,但默认情况下不会得到&#34;&#34;因为那是不明智的。

另一个有用的用例是父接口处于不稳定状态,并且tue代码库很大。它具有功能齐全的实现。使用默认值的孩子必须重复签名并明确转发给它。那些想要覆盖的人只是覆盖。

当基类sigrnature发生更改时,代码将无法编译,除非每个子项显式调用默认值或正确覆盖。在override关键字之前,这是确保您不会意外创建新虚拟函数而不是覆盖父级的唯一方法,并且它仍然是在父类型中强制实施策略的唯一方法。

答案 6 :(得分:0)

必须定义析构函数,即使它是纯虚拟的。如果没有定义析构函数,编译器将生成一个析构函数。

编辑:你不能在没有定义的情况下声明析构函数,会导致链接错误。

答案 7 :(得分:0)

无论如何,您可以从派生类调用函数体。 您可以实现纯虚函数的主体以提供默认行为,同时您希望派生类的设计者显式使用该函数。