Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overwriting pure virtual functions by 'using' a separately inherited method

Tags:

c++

Just a small annoyance really as I can work around the problem by wrapping the derived function instead of using the 'using' keyword but why doesn't the following work (the compiler tells me that 'get_elem' is still pure virtual in 'Bar' class).

class Elem {};

class DerivedElem : public Elem {};

class Foo {

  public:
    virtual Elem&  get_elem() = 0;

};

class Goo {

  protected:
    DerivedElem elem;

  public:
    DerivedElem& get_elem()  { return elem; }

};


class Bar : public Foo, public Goo {

  public:
    using Goo::get_elem;

};

int main(void) {

  Bar bar;

}

Cheers,

Tom

like image 780
Tom Avatar asked Aug 19 '10 02:08

Tom


People also ask

Can we override pure virtual function?

A pure virtual function is a function that must be overridden in a derived class and need not be defined. A virtual function is declared to be “pure” using the curious =0 syntax. For example: class Base {

Are pure virtual functions inherited?

Virtual member functions are inherited. A class derived from an abstract base class will also be abstract unless you override each pure virtual function in the derived class. The compiler will not allow the declaration of object d because D2 is an abstract class; it inherited the pure virtual function f() from AB .

Can pure virtual function be overridden in child class?

No. This is not possible.

Is it necessary to override every virtual function?

It is not mandatory for the derived class to override (or re-define the virtual function), in that case, the base class version of the function is used.


3 Answers

If Goo is a "mixin" designed to implement the interface Foo in a particular way (there could be other mixins with other implementations), then Goo can derive from Foo (instead of Bar doing so).

If Goo isn't designed to implement the interface Foo, then it would be a terrible mistake to treat Bar as though it had implemented that pure virtual function, when it fact it just happens to have a function of the same signature. If you want implicit interfaces and "duck" typing in C++ you can do it, but you have to do it with templates. Rightly or wrongly, pure virtual functions are for explicitly declared interfaces, and Goo's get_elem function is not explicitly declared to implement Foo::get_elem. So it doesn't.

I guess that doesn't explain why in principle the language couldn't define using Goo::get_elem for Foo;, or some such declaration in Bar, to avoid the need for Bar to contain a lot of boilerplate wrapping the call.

You can maybe do something with templates to allow Goo to support this to some extent, without really knowing about Foo:

template <typename T>
class Goo : public T {

  protected:
    DerivedElem elem;

  public:
    DerivedElem& get_elem()  { return elem; }
};

class Bar : public Goo<Foo> {};

class Baz : public Goo<Fuu> {};

Where Fuu is some other interface that has a get_elem function. Obviously it's then the responsibility of the author of Bar to ensure that Goo really does implement the contract of Foo, and the same for Baz checking the contract of Fuu.

By the way, this form of covariance is a bit dodgy. Looking at Foo, someone might expect the expression bar.get_elem() = Elem() to be valid, and it isn't, so LSP is violated. References are funny like that. ((Foo &)bar).get_elem() = Elem() is valid but in general doesn't work! It only assigns to the Elem sub-object, and for that matter so does ((Foo &)bar).get_elem() = DerivedElem(). Polymorphic assignment is basically a nuisance.

like image 168
Steve Jessop Avatar answered Sep 27 '22 20:09

Steve Jessop


In your example, the Foo and Goo are separate classes. In Bar, the method get_elem from Goo is not at all the same with the one in Foo, even if their signature match.

By having using Goo::get_elem, you simply tell the compiler to resolve unqualified call to get_elem() to the one in Goo.

like image 24
RichN Avatar answered Sep 27 '22 18:09

RichN


You've encountered one of the many odd corners of C++. In this case C++ does not consider two virtual functions inherited from different classes to be the same function even though they have the same name and type signature.

There are some good reasons for C++ to act this way. For example, it's frequently the case that those two functions really aren't the same, despite the fact they have the same name and type signature. The semantic meaning of the two functions are different.

Here is an example:

namespace vendor1 {

class Circle {
 public:
    virtual ::std::size_t size() const { return sizeof(*this); }
};

} // namespace vendor1


namespace vendor2 {

class Shape {
 public:
    virtual double size() const = 0;
};

class Circle : public Shape {
 public:
    virtual double size() const { return radius_ * radius_ * M_PI; }
};

} // namespace vendor2

And then you try this:

namespace my_namespace {

class Circle : public ::vendor1::Circle, public ::vendor2::Circle {
 //  Oops, there is no good definition for size
};

So you have to resort to this:

namespace my_namespace {

class Vendor1Circle : public ::vendor1::Circle {
 public:
    virtual ::std::size_t data_structure_size() const { return size(); }
};

class Vendor2Circle : public ::vendor2::Circle {
 public:
    virtual double area() const { return size(); }
};

class Circle : public Vendor1Circle, public Vendor2Circle {
 // Now size is still ambiguous and should stay that way
 // And in my opinion the compiler should issue a warning if you try
 // to redefine it
};

So, C++ has good reason to treat virtual functions with the same type signature (the return type is not part of the type signature) and name from two different bases as different functions.

As far as using goes... All a using directive says is "Add the names from this other namespace to this namespace as if there were declared here.". This is a null concept as far as virtual functions are concerned. It merely suggests that any ambiguity when using a name should be resolved a different way. It only declares a name, it doesn't define the name. In order for a virtual function to be overridden a new definition is required.

OTOH, if you put in a simple thunk redefinition inline like this:

class Bar : public Foo, public Goo {

  public:
    virtual DerivedElem& get_elem()  { return Goo::get_elem(); }
};

a good compiler should see that and know to not even bother to create the function, and instead just fiddle the virtual table entries to do the right thing. It may need to actually emit code for it and have the symbol available in case its address is taken, but it should still be able to simply fiddle the virtual table into having the function completely disappear when called through a Foo *.

like image 26
Omnifarious Avatar answered Sep 27 '22 18:09

Omnifarious