【问题标题】:Multiple templated interface inheritance name hiding多个模板化接口继承名称隐藏
【发布时间】:2017-02-18 19:16:23
【问题描述】:

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

这是课程

#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&lt;ReqA, RespA&gt; client);void foo(IClient&lt;ReqA, RespA, ReqB, RespB&gt; 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 接口中是纯函数,但我将它们全部“命名”到 using Derived::method 的类中,所以它们可以是调用ClientAdapter时解决。

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

我怎样才能让这段代码编译并做正确的事情?

这里有一个wandbox 示例来查看错误并尝试修改它。

谢谢

【问题讨论】:

  • 你还有IClient&lt;Q, A&gt;被继承两次的问题。一次是ClientAdapter&lt;Q, A&gt;,一次是IClient&lt;Q, A, ...&gt;。所以ClientAdapter&lt;Q, A, ...&gt; 不能隐式转换为IClient&lt;Q, A&gt;,因为转换会不明确。不确定您的继承是否是最好的方法,但要解决这个特殊问题,在任何地方继承 virtual IClient&lt;Q, A&gt;virtual IClient&lt;Q, A, ...&gt; 就可以了。那么这个类也不应该是纯粹的了。
  • 所以IClient&lt;A, B, C, D, E, F&gt;继承了IClient&lt;C, D, E, F&gt;,但是为什么不继承IClient&lt;A, B, C, D&gt;呢?甚至IClient&lt;A, B, E, F&gt;。这种方法不能很好地扩展。可能没有继承,但是有一个类IClientAccessor&lt;X1, X2, Y1, Y2, ...&gt;,其构造函数模板采用IClient&lt;...&gt;,并自动检查客户端的参数列表是否是访问器参数的超集。如果是这样,它接受客户端。否则,它会拒绝客户端(不能从中转换)。否则它将像您的客户端适配器一样工作。
  • @JohannesSchaub-litb 多重继承的问题是当前实现的“缺陷”。这是一个没有多重继承的链接melpon.org/wandbox/permlink/DfbaTCWRRQ9Xh2OW你能详细说明你的第二条评论吗?如何在 IClientAccessor 中定义虚拟纯方法?或者您是否设想为每个参数存储和实例化 IClient,然后只定义获取特定实例的方法?但它不会是虚拟的,会不会(因为它变成了一个模板化的方法)?
  • 可能看起来像这样:coliru.stacked-crooked.com/a/00e5495652393a60。虽然没有继承。
  • @JohannesSchaub-litb 我明白了。不幸的是,我想使用我在这篇文章中尝试编写的解决方案来重构我的团队正在使用的一些代码(目前他们只是绕过客户端)。我认为传递带有变体的 std::function 不会使代码更清晰。但确实你的解决方案有效。如果没有办法将接口放在一起,我想我只会使用 2 个参数模板,并且在调用 foo 时只使用几个参数。不太漂亮,但绝对更清楚正在发生的事情,即使对于不太精通 C++ 的人来说也是如此

标签: c++ templates inheritance multiple-inheritance


【解决方案1】:

简化复制:

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 覆盖递归减少的IClient 中的sendRequest 方法这一事实并不重要,因为这是IClient 的两个不同实例。

如果您几乎在所有情况下都继承IClient,那么问题就会因为inheritance via dominance (Live demo) 而消失。请注意,这种继承模式会在 MSVC 中引发警告 C4250。您可以放心地忽略它。

【讨论】:

  • 有效!我将阅读有关虚拟继承的信息,我什至不知道它存在。使用它有什么缺点吗?我问这个是因为 MSVC 会生成警告,所以我猜它有些“危险”..
  • 虚拟继承是必不可少的,你必须了解它。 MSVC 会生成有关 dominance 的警告,这是使用虚拟继承的一种特定方式。这并不危险,但该功能是模糊的,足以让 MS 工程师认为有必要警告您。这个网站上有很多关于这个警告的讨论(搜索它)。没有共识。我个人认为应该永久禁用该警告。其他编译器不会打扰。
【解决方案2】:

为了完整起见,我将添加另一个答案,这是我在解决我的问题后发现的,并受到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

【讨论】:

    猜你喜欢
    • 2011-03-22
    • 1970-01-01
    • 2016-01-01
    • 1970-01-01
    • 2014-10-06
    • 2011-10-06
    • 2015-06-04
    • 1970-01-01
    • 2017-01-12
    相关资源
    最近更新 更多