5

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)?

Sebastian Hoffmann
  • 2,815
  • 1
  • 12
  • 22
  • 2
    Even if `A::foo()` is not pure virtual, `virtual void foo() = 0;` in `struct B` makes `struct B` abstract, i.e. it may not be instanced. [Demo on coliru](http://coliru.stacked-crooked.com/a/9ed3a18d56ef8c13) The sense of repeating the pure virtual in `B` (as it is currently in the exposed code) might be for documentation. Even although it is not really necessary it doesn't cause any harm. – Scheff's Cat Jun 29 '20 at 10:24
  • 2
    Does this answer your question? [Pure virtual methods in derived abstract class](https://stackoverflow.com/questions/15603579/pure-virtual-methods-in-derived-abstract-class) – donkopotamus Jun 29 '20 at 10:26
  • It doesn't change a thing, but I'd skip `virtual` in the subclasses and put `override` on both of them. You may even make `A::foo` a pure virtual _and_ have a default implementation. That's quite common for base class destructors. – Ted Lyngmo Jun 29 '20 at 10:27

2 Answers2

8

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    To clarify: Redeclaring a non-pure function as pure will "get rid" of the original implementation provided by the parent but otherwise doesn't change a thing (except for guarateeing that the class will be abstract)? – Sebastian Hoffmann Jun 29 '20 at 10:32
  • @SebastianHoffmann not sure what you mean with "otherwise", what else should it affect? – 463035818_is_not_an_ai Jun 29 '20 at 10:33
  • 1
    @SebastianHoffmann in case you refer to a vtable.. Thats an implementation detail. Afaik the standard makes no mention of a vtable. Instead it specifies how virtual methods and overriding is supposed to work (and most/all compilers use a vtable to implementat that) – 463035818_is_not_an_ai Jun 29 '20 at 10:34
  • sure its an implementation detail, yet if I understood your answer correctly, `foo()` will always refer to the same virtual function (i.e `A::foo`), no matter whether it has been redeclared as pure in a subclass. So a call to it will always happen through the vftable of A (in any sane compiler). I think we're slightly talking past each other, but your comments helped clarifying the situation for me anyways. Thank you. – Sebastian Hoffmann Jun 29 '20 at 10:40
  • @SebastianHoffmann wait a sec.. I just discovered that there is a difference ;) Will update the answer – 463035818_is_not_an_ai Jun 29 '20 at 10:51
  • @SebastianHoffmann "So a call to it will always happen through the vftable of A" how would you notice if that didn't happen? I don't really understand what you think could go wrong – 463035818_is_not_an_ai Jun 29 '20 at 10:59
  • I'm not concerned that anything can go wrong. I'm just curious as how this kind of code behaves. Good catch with the function pointers btw. – Sebastian Hoffmann Jun 29 '20 at 11:34
  • @SebastianHoffmann it would behave the same also if there was no vtable. So strictly speaking the answer to "...will always happen through the vftable of A?" is No – 463035818_is_not_an_ai Jun 29 '20 at 11:35
0

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;
}
Vincent
  • 57
  • 6
  • FYI - The redeclaration is necessary because apply(int*, double*) in the base class is private. change it to protected, and reference it as Transition::apply() in MTransition and the duplicate is no longer required. https://godbolt.org/z/bsWGK3jos – Brad May 21 '21 at 03:38
  • I updated my code. I still get an error (at link time now). See the edited post. – Vincent May 28 '21 at 21:59