多个模板化接口继承名称隐藏

时间:2017-02-18 19:16:24

标签: c++ templates inheritance multiple-inheritance

我有一个带有模板化方法的类,它会发出请求。

这是班级

#include <iostream>

struct Client
{
    template<class Request, class Response>
    void sendRequest(const Request& q , Response& a)
    {
        std::cout << q << " " << a << "\n";
    }
};

现在,发布未知类型请求的方法并不常见。大多数方法都会发送一些请求类型,我希望这些方法能够使用接口来使代码可测试并清楚地表达它们对这些请求的依赖性。

但我们都知道我们不能将模板化方法虚拟化。

所以我想创建一个接口,用于将类用于特定的请求和响应。

这里是界面

template<class Q, class A, class ... Rest>
struct IClient : public IClient<Rest ...>
{
    using IClient<Rest ...>::sendRequest;
    virtual void sendRequest(const Q& , A&) = 0;

    ~IClient() = default;
};

template<class Q, class A>
struct IClient<Q, A>
{
    virtual void sendRequest(const Q& , A&) = 0;
    ~IClient() = default;
};

这个想法是有一个专门化,它采用2种参数类型,并为这2种类型定义sendRequest方法。 我将模板参数推送到类中,因此该方法可以是虚拟的。

泛型类派生自使用模板参数包的前2个参数创建的特化,以便我们可以定义多个请求和响应类型。

一个函数可以像void foo(IClient<ReqA, RespA> client);void foo(IClient<ReqA, RespA, ReqB, RespB> client);一样使用它,依此类推。

此时,从调用代码中可以清楚地知道该函数将发出哪些请求。如果我直接传递客户端,我将丢失该信息(除了无法模拟客户端)。

到目前为止一切顺利。

从现在开始,我们将此作为我们的测试代码

void foo(IClient<int, char, int, int, double, float>& client)
{
    int i = 10;
    char c = 'd';
    double d = 3.14;
    float f = 100.5f;
    client.sendRequest(i, c);
    client.sendRequest(i, i);
    client.sendRequest(d, f);
}

int main()
{
    Client client;
    ClientAdapter<int, char, int, int, double, float> adapter(client);
    foo(adapter);
}

但是我不想手动创建一个简单地将呼叫转发给客户的课程,我的时间比这更有价值。 让编译器为我工作很难!

这里是创建一个接受客户端并遵守接口的适配器的代码。

template<class Q, class A, class ... Rest>
struct ClientAdapter: public IClient<Q, A, Rest ...>, public ClientAdapter<Rest ...>
{
    using ClientAdapter<Rest ...>::sendRequest;

    Client& client;
    ClientAdapter(Client& c) : ClientAdapter<Rest...>(c), client(c)
    {

    }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapter() = default;
};

template<class Q, class A>
struct ClientAdapter<Q, A> : public IClient<Q, A>
{
    Client& client;
    ClientAdapter(Client& c) : client(c) { }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapter() = default;
};

这次的想法是为每个接口定义一个适配器,给定用户使用的请求和响应类型,从它们派生,从而能够使用该类代替接口。

这里的梦想被打破了。

 In function 'int main()':
 70:55: error: cannot declare variable 'adapter' to be of abstract type 'ClientAdapter<int, char, int, int, double, float>' 
 30:8: note: because the following virtual functions are pure within 'ClientAdapter<int, char, int, int, double, float>': 
25:18: note: void IClient<Q, A>::sendRequest(const Q&, A&) [with Q = double; A = float]
25:18: note: void IClient<Q, A>::sendRequest(const Q&, A&) [with Q = double; A = float]
17:18: note: void IClient<Q, A, Rest>::sendRequest(const Q&, A&) [with Q = int; A = int; Rest = {double, float}]

如果我将适配器定义为

template<class Q, class A, class ... Rest>
struct ClientAdapter: public ClientAdapter<Q, A>, public ClientAdapter<Rest ...>

我得到了

In function 'int main()':
71:16: error: invalid initialization of reference of type 'IClient<int, char, int, int, double, float>&' from expression of type 'ClientAdapter<int, char, int, int, double, float>'
56:6: note: in passing argument 1 of 'void foo(IClient<int, char, int, int, double, float>&)'

我认为是因为ClientAdapter不是来自IClientAdapter的派生类型(但只是在它之前的层次结构中派生了所有类)。

此时我对C ++的了解停止了(我最近刚开始)并且我不知道如何解决这个问题。

对我而言,第一个错误没有意义,因为虽然这些函数在IClient界面中是纯粹的,但我确实没有隐藏&#34;所有这些都是using Derived::method进入班级的,因此在调用ClientAdapter时可以解决这些问题。

我也想知道为什么该方法在错误中出现2次相同的参数。

如何让这段代码编译并做正确的事?

这是一个wandbox示例,用于查看错误并尝试使用它。

谢谢

2 个答案:

答案 0 :(得分:2)

简化复制:

struct A {
    virtual void foo() = 0;
};

struct B {
    virtual void foo() {}
};

struct C : A, B {
    using B::foo;
};

C c; // error: C is abstract

问题在于using B::foo不会覆盖A::foo

它与您的代码有什么关系?

template<class Q, class A, class ... Rest>
struct ClientAdapter: public IClient<Q, A, Rest ...>, public ClientAdapter<Rest ...>
{   
    using ClientAdapter<Rest ...>::sendRequest; ...        
    void sendRequest(const Q& q, A& a) override ...
};

您使用多个纯虚方法继承IClient但仅覆盖一个。其余的仍然是纯粹的,并使ClientAdapter抽象。在递归缩减ClientAdapter中递归缩减sendRequest覆盖IClient方法的事实并不重要,因为这是IClient的两个不同实例。

如果您在所有情况下虚拟地继承IClient,问题就会因inheritance via dominanceLive demo)而消失。请注意,这种继承模式会在MSVC中引发警告C4250。你可以放心地忽略它。

答案 1 :(得分:0)

为了完整起见,我会在我的问题解决后添加另一个答案,并受到Virtual inheritance: Why does it work when only one base class has "virtual" keyword? Is there a better way?第一个答案的启发。

此解决方案避免使用虚拟继承。

我们的想法是使用模板线性化结构并下推接口继承。

IClient保持不变。

template<class Interface, class Q, class A, class ... Rest>
struct ClientAdapterImpl: public ClientAdapterImpl<Interface, Rest ...>
{
    using ClientAdapterImpl<Interface, Rest ...>::sendRequest;

    Client& client;
    ClientAdapterImpl(Client& c) : ClientAdapterImpl<Interface, Rest...>(c), client(c)
    {

    }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapterImpl() = default;
};

template<class Interface, class Q, class A>
struct ClientAdapterImpl<Q, A> : public Interface
{
    Client& client;
    ClientAdapterImpl(Client& c) : client(c) { }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapterImpl() = default;
};

template<class Q, class A, class ... Rest>
struct ClientAdapter: public ClientAdapterImpl<IClient<Q, A, Rest...>, Q, A, Rest ...>
{
     ClientAdapter(Client& c) : ClientAdapterImpl<IClient<Q, A, Rest...>, Q, A, Rest...>(c)
    {

    }

    ~ClientAdapter() = default;

}

此解决方案将接口作为模板参数传递给实现模板,该模板继承自链的末尾,有效地创建了一个平面层次结构。

interface0 <-- everything derive from this
    |
interface1
    |
interface2
    \
     \
   implementation0
          |
   implementation1
          ^
          |
   implementation3 <-- this derives from everything