仅功能的多重继承 - 没有虚拟和 CRTP

multiple inheritance for function only - without virtual and CRTP

如何只为函数做多重继承?

这里有类似的问题:-

这是一个示例代码 (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

这是我的尝试 (wandbox demo)。我滥用 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>{};

可以用,但看起来不够优雅。
此外,实现必须在 header 中(因为 OAOB 是模板 classes)。

有没有更好的方法?或者这是要走的路吗?

抱歉,如果这个问题太新手或已经问过。我是C++初学者。

编辑

Give extended example of using please.

在 ECS 中,它在某些情况下会很有用 :-

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

开始讨论。

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

是这样的吗?

  • 必须共享基地的数据class - 检查
  • 没有虚函数(假设 vtable 很昂贵)- 检查
  • 避免虚拟继承 - 检查
  • 实现必须能够驻留在 .cpp-check
  • c++14 是允许的 - 检查。使用了 c++11。

#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();

}

另一种方式,使用组合和访问 class 来提供对受保护成员的访问。此示例显示如何将 database 上的工作推迟到另一个编译单元:

#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();

}

这里的问题是 database 字段是 class O 的成员。因此如果没有虚拟继承,A 和 B 将各自拥有自己的 database 副本。所以你必须找到一种方法来强制 A 和 B 共享相同的值。例如,您可以使用在受保护的构造函数中初始化的引用字段:

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

按预期显示:

2

以上代码没有使用虚继承,没有虚函数,也没有模板,所以方法可以安全地在cpp文件中实现。 class AB 实际上使用了其双亲的方法,并且对其基础数据仍然有一致的看法。事实上,它通过在最派生的 class 中构建公共数据并通过其父级中的受保护构造函数注入来模拟显式虚拟继承。

我们从定义可以打印的东西和可以设置的东西的概念开始:

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

然后我们声明 O 以及您可以支持的操作:

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

其中最难的部分是访问控制。

我确保无法调用 do_set,除非通过 has_set::set

这就是所有 token 的意义所在。如果您愿意只说 "don't call the do_ functions"(也可以给它们另一个名字,例如 private_impl_set),您可以去掉它们和它们的开销。

Live example.