1

How to do multiple inheritance just for function?

  • must share data of the base class
  • no virtual function (assume that vtable is expensive)
  • avoid virtual inheritance
  • implementation must be able to reside in .cpp
  • c++14 is allowed

Here are similar questions :-

Here is a sample code (coliru demo) :-

class O{
    protected: int database=0;  
};
class A : public O{
    public: void print(){
        std::cout<<database<<std::endl;
    }
};
class B : public O{
    public: void set(int s){
        database=s+1;
    }
};
class AB : public O{
    public: void print(){//duplicate
        std::cout<<database<<std::endl;
    }
    public: void set(int s){//duplicate
        database=s+1;
    }
};
//AB ab;  ab.set(1); ab.print(); // would print 2

Here is my attempt (wandbox demo). I abuse CRTP :( :-

class O{
    public: int database=0; 
};
template<class T>class OA{
    public: void print(){
        std::cout<<static_cast<T*>(this)->database<<std::endl;
    }
};
template<class T>class OB{
    public: void set(int s){
        static_cast<T*>(this)->database=s+1;
    }
};
class A :public O,public OA<A>{};
class B :public O,public OB<B>{};
class AB :public O,public OA<AB>,public OB<AB>{};

It works, but it looks inelegant.
Furthermore, implementation must be in header (because OA and OB are template classes).

Are there better approaches? Or is this the way to go?

Sorry if it is too newbie question or already asked. I am a C++ beginner.

Edit

Give extended example of using please.

In ECS, it would be useful in some cases :-

class O{
    protected: EntityHandle e;  
};
class ViewAsPhysic : public O{                     //A
    public: void setTransform(Transformation t){
        Ptr<PhysicTransformComponent> g=e;
        g->transform=t;
    }
};
class ViewAsLight : public O{                      //B
    public: void setBrightness(int t){    
        Ptr<LightComponent> g=e;
        g->clan=t;
    }
};
class ViewAsLightBlock : public O{                 //AB
    //both functions 
};
cppBeginner
  • 1,114
  • 9
  • 27
  • The upper code example is much better and by no means less efficient. If you want to avoid the pieces marked as duplicate, write them in the base class (non-virtual, if you prefer) – davidhigh Jul 31 '17 at 09:34
  • @davidhigh It contains duplicated code -> cause maintainability problem. `:(` – cppBeginner Jul 31 '17 at 09:35
  • If you do not need virtual calls, compiler will make devirtualization optimisation, so you can use inheritance without cost like: `AB *ab= new AB; ab->print();` – user5821508 Jul 31 '17 at 09:35
  • 1
    @cppBeginner: I was just adding your point in, but you were faster :-) – davidhigh Jul 31 '17 at 09:36
  • @user5821508 sometimes true, sometimes not true (vtable look-up need) : see https://stackoverflow.com/questions/5553850/is-there-any-penalty-cost-of-virtual-inheritance-in-c-when-calling-non-virtua . – cppBeginner Jul 31 '17 at 09:37

4 Answers4

2

Something like this?

  • must share data of the base class - check
  • no virtual function (assume that vtable is expensive) - check
  • avoid virtual inheritance - check
  • implementation must be able to reside in .cpp- check
  • c++14 is allowed - check. c++11 used.

 

#include <iostream>

class O {
protected:
    int database = 0;
};

/*
 * the concept of implementing print for a base class
 */
template<class...Bases>
struct implements_print : Bases... {
    void print() const {
        std::cout << this->database << std::endl;
    }
};

/*
 * The concept of implementing set for a base class
 */

template<class...Bases>
struct implements_set : Bases... {
    void set() {
        ++this->database;
    }

};

struct B : implements_set<O> {
};

struct A : implements_print<O> {
};

struct AB : implements_set<implements_print<O>> {
};

int main() {

    A a;
    a.print();

    B b;
    b.set();

    AB ab;
    ab.set();
    ab.print();

}

Another way, using composition and an access class to provide access to the protected member. This example shows how to defer the work on database to another compilation unit:

#include <iostream>

/*
 * this stuff in cpp
 */

namespace implementation
{
    void print(const int& database) {
        std::cout << database << std::endl;
    }

    void set(int& database) {
        ++database;
    }
}


/*
 * this stuff in header
 */

struct OAccess;

class O {
private:
    int database = 0;
    friend OAccess;
};

struct OAccess {
    template<class Host>
    constexpr decltype(auto) database(Host &host) const { return (host.database); } // note: () makes reference

    template<class Host>
    constexpr decltype(auto) database(Host const &host) const { return (host.database); } // note: () makes reference
};

/*
 * the concept of implementing print for a derived class
 */
template<class Host>
struct implements_print {
    void print() const {
        OAccess access;
        implementation::print(access.database(self()));
    }

private:
    decltype(auto) self() const { return static_cast<Host const &>(*this); }
};

/*
 * The concept of implementing set for a derived class
 */

template<class Host>
struct implements_set {
    void set() {
        OAccess access;
        implementation::set(access.database(self()));
    }

private:
    decltype(auto) self() { return static_cast<Host &>(*this); }
};

template<template<class> class...Impls>
struct OImpl : Impls<OImpl<Impls...>> ..., O {
};

using B = OImpl<implements_set>;
using A = OImpl<implements_print>;
using AB = OImpl<implements_print, implements_set>;


int main() {

    A a;
    a.print();

    B b;
    b.set();

    AB ab;
    ab.set();
    ab.print();

}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I don't think that `print()` can move to .cpp, because `implements_print` is a template class. Did I miss something? – cppBeginner Jul 31 '17 at 10:26
  • it can call another function that lives in a cpp to do the work on O. – Richard Hodges Jul 31 '17 at 10:36
  • @cppBeginner see second example that uses composition - it defers the actual printing to a free function in another compilation unit. – Richard Hodges Jul 31 '17 at 10:41
  • Thank, it is useful and painful at the same time. (+1) In practice, should I use virtual function / template instead? I start to think that it is unavoidable to be dirty in some ways. – cppBeginner Jul 31 '17 at 11:14
  • 2
    @cppBeginner I don't know what your real use case is. crtp+inheritence is usually a good all-round tool. I suspect that you're working on a false premise if you think virtual functions are "slow". They are not. They require one more memory fetch than a non-virtual function. Any other polymorphic alternative, such as a switch or carrying a function pointer is no faster. – Richard Hodges Jul 31 '17 at 11:26
  • `OAccess` -- how is that distinct from making the fields public? – Yakk - Adam Nevraumont Jul 31 '17 at 19:10
  • @Yakk it requires deliberate invocation. It's not perfect, but it's less hassle than a bullshit befriended key argument. It's a technique used in boost::serialization. – Richard Hodges Jul 31 '17 at 19:12
2

The problem here is that the database field is member of class O. So without virtual inheritance, A and B will have each their own copy of database. So you must find a way to force A and B to share same value. You could for example use a reference field initialized in a protected constructor:

#include <iostream>

class O{
    int _db;
    protected: int &database;
    O(): database(_db) {};
    O(int &db): database(db) {};
};
class A : public O{
    public: void print(){
        std::cout<<database<<std::endl;
    }
    A() {}                                // public default ctor
    protected: A(int& db): O(db) {};      // protectect ctor
};
class B : public O{
    public: void set(int s){
        database=s+1;
    }
    B() {}                                // public default ctor
    protected: B(int& db): O(db) {};      // protectect ctor
};
class AB : public A, public B {
    int _db2;
    public: AB(): A(_db2), B(_db2) {};    // initialize both references to same private var
};

int main() {
    AB ab;
    ab.set(1);
    ab.print();
    return 0;
}

displays as expected:

2

Above code uses no virtual inheritance, no virtual function and no templates, so method can safely implemented in cpp files. The class AB actually uses methods from its both parents and has still a coherent view on its underlying data. In fact it simulates an explicit virtual inheritance by building the common data in the most derived class and injecting in through protected constructors in its parents.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

We start with defining the concepts of things that can print and things that can be set:

namespace util {
  template<class Base, class Derived, class R=void>
  using if_base = std::enable_if_t< std::is_base_of< std::decay_t<Base>, std::decay_t<Derived>>::value, R >;
  struct stub {};
}
namespace concepts {
  template<class Token>
  void do_print(Token, util::stub const&)=delete;
  template<class Token>
  void do_set(Token, util::stub&, int)=delete;

  struct has_print {
    struct token { friend struct has_print; private: token(int){} };
    template<class T>
    friend util::if_base<has_print, T> print(T const& t) {
      do_print(get_token(), t);
    }
  private: static token get_token() { return 0; }
  };
  struct has_set {
    struct token { friend struct has_set; private: token(int){} };
    template<class T>
    friend util::if_base<has_set, T> set(T& t,  int x) {
      do_set(get_token(),t, x);
    }
  private: static token get_token() { return 0; }
  };
}

We then declare O and the operations you can support on it:

namespace DB { 
    class O;
    void do_print(::concepts::has_print::token, O const& o);
    void do_set(::concepts::has_set::token, O& o, int);
    class O{
        protected: int database=0;  
        friend void do_print(::concepts::has_print::token, O const&);
        friend void do_set(::concepts::has_set::token, O&, int);
    };

    class A : public O, public concepts::has_print {
    };
    class B : public O, public concepts::has_set {
    };
    class AB : public O, public concepts::has_print, concepts::has_set {
    };
}
void DB::do_print(::concepts::has_print::token, O const& o ) { std::cout << o.database << std::endl; }
void DB::do_set(::concepts::has_set::token, O& o, int x) { o.database = x+1; }

The hard part of this is the access control.

I ensure it isn't possible to call do_set except through has_set::set.

That is what all those tokens are about. You can strip them out and their overhead if you are willing to just say "don't call the do_ functions" (and maybe give them another name, like private_impl_set).

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

To start discussion.

class O
{
    // no virtual destructor. So cant use polymorphic deletion
    // like :
    // O *o = new AB;
    // delete o;
    protected: int database=0;  
};
class A : virtual public O{
    public: void print(){
        std::cout<<database<<std::endl;
    }
};
class B : virtual public O{
    public: void set(int s){
        database=s+1;
    }
};
class AB : protected A, protected B{}; // no vtable

void foo() {
  AB ab;
  ab.print(); // won't perform virtual call.
}
user5821508
  • 332
  • 2
  • 10