Redeclaring pure virtual function

Viewed 465

I recently came across code of the following kind:

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

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

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

Is there any purpose in redeclaring a pure virtual method?

Even more importantly: Does this change semantics? I.e is B::foo shadowing A::foo and introducing a new vftable?

Whats happens in the above example if A::foo is actually not pure virtual but provides an implementation (still compiles for me)?

2 Answers

Look at the example from 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)
}

Is there any purpose in redeclaring a pure virtual method?

There is a purpose in declaring a virtual method as pure in a derived class: Abstract2 can decalre g to be pure virtual even if it is not pure in Concrete.

Does this change semantics? I.e is B::foo shadowing A::foo and introducing a new vftable?

No.

Whats happens in the above example if A::foo is actually not pure virtual but provides an implementation (still compiles for me)?

See the example.

You do not have to delcare a method as virtual in a derived class. When it is declared virtual in the base it is also virtual in derived classes. In a nutshell, the =0 just tells the compiler that there is not necessarily a definition and that the class is abstract. Your example is the same as:

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

struct BB : public AA { };         // abstract

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

The only difference I am aware of is the type of 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";

Output is:

100
010

That is, redeclaring foo in B introduces the name foo directly in B. B::foo is a member function of B. On the other hand in my example BB::foo is the method from AA. However, this doesn't affect C::foo.

I found an example where redeclaring pure virtual method is required. Both gcc 10 and clang 11 requires the repeated declaration when the pure virtual method is used in the implementation of a descendant class itself abstract. Here is an example :

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

As such, this programs does not link. I get the following error:

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)

And it is worst if I try other call to apply in main (compilation error in this case, linked to the warning).

To make it works, I need to write the MTransition class like this:

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

But I do not succeed into calling t.apply(&d1, &d2); in main. It seems to me that the definition of apply(int*p, double*e) in TransitionTest hides (as stated by the warning) the other apply that should have been inherited.

EDIT (again): I found the solution in c++ overloaded virtual function warning by clang? When overloading (or partially overwriting) a method in a derived class, one needs to use the using Base::method to keep the other overloaded methods.

It gives:

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