关于c ++:仅用于函数的多重继承 – 没有虚拟和CRTP

multiple inheritance for function only - without virtual and CRTP

如何只为函数进行多重继承?

  • 必须共享基类的数据
  • 无虚拟功能(假设vtable很贵)
  • 避免虚拟继承
  • 实现必须能够驻留在.cpp中
  • 允许C++ 14

以下是类似的问题:

  • 菱形形状中的多个继承仅具有函数-使用虚拟继承。虚拟继承通常是不好的和昂贵的。
  • 无虚拟继承的多重继承-侧重于语法和编译,而不是编程技术。
  • 在C++(CRTP)、CRTP和多级继承中的多级继承,用CRTP和多重继承(C++ 03)消除冗余,并使用具有虚拟继承的CRTP实现。

下面是一个示例代码(coliru demo):-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

这是我的尝试(魔盒演示)。我滥用crtp :(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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{};
class AB :public O,public OA<AB>,public OB<AB>{};

它能用,但看起来不漂亮。此外,实现必须在头文件中(因为OAOB是模板类)。

有更好的方法吗?还是这样?

对不起,如果这是太新的问题或已经问了。我是一个C++初学者。

编辑

Give extended example of using please.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
};


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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和您可以支持的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的目的。如果你只想说"不要调用do_函数"(或者给它们起另一个名字,比如private_impl_set函数),就可以去掉它们和它们的开销。

活生生的例子。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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;
}

按预期显示:

1
2

上面的代码不使用虚拟继承、虚拟函数和模板,因此方法可以安全地在cpp文件中实现。AB类实际上使用它的两个父类的方法,并且仍然对其基础数据有一个一致的视图。实际上,它通过在最派生类中构建公共数据并通过其父类中受保护的构造函数注入来模拟显式虚拟继承。


像这样?

  • 必须共享基类的数据-检查
  • 无虚拟功能(假设vtable很贵)-检查
  • 避免虚拟继承-检查
  • 实现必须能够驻留在.cpp-check中
  • 允许C++ 14检查。使用C++ 11。

nbsp;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#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();

}

另一种方法,使用组合和访问类提供对受保护成员的访问。这个例子演示了如何将database上的工作推迟到另一个编译单元:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#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();

}


开始讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.
}