重新声明纯虚函数

时间:2020-06-29 10:21:01

标签: c++

我最近遇到了以下类型的代码:

struct A {
    virtual void foo() = 0;
};

struct B : public A {
    virtual void foo() = 0;
};

struct C : public B {
    virtual void foo() override {
    }
};

重新声明纯虚拟方法是否有目的?

更重要的是:这会改变语义吗?即B::foo是否在A::foo的阴影下并引入了新的vftable?

如果A::foo实际上不是纯虚拟的,但提供了一个实现(仍为我编译),在上面的示例中会发生什么?

2 个答案:

答案 0 :(得分:6)

请看cppreference中的示例:

struct Abstract {
    virtual void f() = 0; // pure virtual
}; // "Abstract" is abstract
 
struct Concrete : Abstract {
    void f() override {} // non-pure virtual
    virtual void g();     // non-pure virtual
}; // "Concrete" is non-abstract
 
struct Abstract2 : Concrete {
    void g() override = 0; // pure virtual overrider
}; // "Abstract2" is abstract
 
int main()
{
    // Abstract a; // Error: abstract class
    Concrete b; // OK
    Abstract& a = b; // OK to reference abstract base
    a.f(); // virtual dispatch to Concrete::f()
    // Abstract2 a2; // Error: abstract class (final overrider of g() is pure)
}

重新声明纯虚拟方法是否有目的?

有一个目的是在派生类中将虚拟方法声明为纯方法:Abstract2可以将g降级为纯虚拟方法,即使它在Concrete中不是纯方法。

这会改变语义吗?即是B :: foo遮盖A :: foo并引入新的 vftable?

否。

如果A :: foo实际上不是纯虚拟的,但提供了一个实现(仍然为我编译),在上面的示例中发生了什么?

请参见示例。

您不必在派生类中将方法像virtual那样传递。当在基中将其声明为virtual时,它在派生类中也为virtual。简而言之,=0只是告诉编译器不一定有定义,并且该类是抽象的。您的示例与以下示例相同:

struct AA {                       // abstract
    virtual void foo() = 0;         
};

struct BB : public AA { };         // abstract

struct CC : public BB {            // concrete
    void foo() override {}
};

我知道的唯一区别是BB::foo的类型:

// above
std::cout << std::is_same< decltype(&BB::foo), void (AA::*)()>::value;
std::cout << std::is_same< decltype(&BB::foo), void (BB::*)()>::value;
std::cout << std::is_same< decltype(&BB::foo), void (CC::*)()>::value << "\n";
// your example
std::cout << std::is_same< decltype(&B::foo), void (A::*)()>::value;
std::cout << std::is_same< decltype(&B::foo), void (B::*)()>::value;
std::cout << std::is_same< decltype(&B::foo), void (C::*)()>::value << "\n";

输出为:

100
010

也就是说,在foo中重新声明B会在foo中直接引入名称BB::fooB的成员函数。另一方面,在我的示例中,BB::fooAA中的方法。但是,这不会影响C::foo

答案 1 :(得分:0)

我发现了一个需要重新声明纯虚方法的例子。 gcc 10 和 clang 11 当纯虚方法用于后代类本身抽象的实现时都需要重复声明。这是一个例子:

class Transition {
  public: 
  virtual int* apply(int*p, double*e) = 0;
  virtual long* apply(double*h, double*e) = 0;
  virtual long* apply(long* l, double *e) { return l;}
};

class MTransition : public Transition {
  public:
  //virtual int* apply(int*p, double*e) = 0; // duplicate from upper class but required
  virtual long* apply(double*h, double*e) {
    int a= *(Transition::apply((int*)h, e)); // not resolved correctly without the duplicate declaration
    return a?nullptr:nullptr;
  }
};

class TransitionTest : public MTransition {
  public: 
  virtual int* apply(int*p, double*e) { return p; }
};

int main() {
  TransitionTest t;
  double /*d1=1.0,*/ d2=2.0;
  int i1=1;
  /*int *pi = t.apply(&d1, &d2); */
  int *pi = t.apply(&i1, &d2);
  return pi?(*pi):0;
}

因此,此程序不链接。我收到以下错误:

clang++ -Wall yo.cpp 
yo.cpp:19:16: warning: 'TransitionTest::apply' hides overloaded virtual function [-Woverloaded-virtual]
  virtual int* apply(int*p, double*e) { return p; }
               ^
yo.cpp:11:17: note: hidden overloaded virtual function 'MTransition::apply' declared here: type mismatch at 1st parameter ('double *' vs 'int *')
  virtual long* apply(double*h, double*e) { 
                ^
1 warning generated.
/usr/bin/ld: /tmp/yo-cb0979.o: in function `MTransition::apply(double*, double*)':
yo.cpp:(.text._ZN11MTransition5applyEPdS0_[_ZN11MTransition5applyEPdS0_]+0x27): undefined reference to `Transition::apply(int*, double*)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

如果我尝试在 main 中对 apply 进行其他调用,情况会更糟(在这种情况下是编译错误,与警告相关联)。

为了使它工作,我需要像这样编写 MTransition 类:

class MTransition : public Transition {
  public:
  virtual int* apply(int*p, double*e) = 0; // duplicate from upper class but required
  virtual long* apply(double*h, double*e) {
    int a= *(apply((int*)h, e)); // not resolved correctly without the duplicate declaration
    return a?nullptr:nullptr;
  }
};

但是我没有成功在 main 中调用 t.apply(&d1, &d2);。在我看来,apply(int*p, double*e)TransitionTest 的定义隐藏了(如警告所述)本应继承的另一个 apply

编辑(再次):我在 c++ overloaded virtual function warning by clang? 中找到了解决方案 当重载(或部分覆盖)派生类中的方法时,需要使用 using Base::method 来保留其他重载的方法。

它给出:

class Transition {
  public:
  virtual int* apply(int*p, double*e) = 0;
  virtual int* apply(double*h, double*e) = 0;
  virtual int* apply(long* l, double *e) { std::cerr << "Transition::apply(long*,double*)" << std::endl; return nullptr;}
};

class MTransition : public Transition {
  public:
  using Transition::apply;
  virtual int* apply(double*h, double*e) {
    std::cerr << "MTransition::apply(double*,double*)" << std::endl;
    int a= *(apply((int*)h, e)); // not resolved correctly without the duplicate declaration
    return a?nullptr:nullptr;
  }
};

class TransitionTest : public MTransition {
  public:
  using MTransition::apply;
  virtual int* apply(int*p, double*e) { std::cerr << "TransitionTest::apply(int*,double*)" << std::endl; return p; }
};

int main() {
  TransitionTest t;
  double d1=1.0, d2=2.0;
  //int i1=1;
  int *pi = t.apply(&d1, &d2);
  //int *pi = t.apply(&i1, &d2); 
  return pi?(*pi):0;
}