【发布时间】: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<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 接口中是纯函数,但我将它们全部“命名”到 using Derived::method 的类中,所以它们可以是调用ClientAdapter时解决。
我也想知道为什么该方法在错误中出现了 2 次相同的参数。
我怎样才能让这段代码编译并做正确的事情?
这里有一个wandbox 示例来查看错误并尝试修改它。
谢谢
【问题讨论】:
-
你还有
IClient<Q, A>被继承两次的问题。一次是ClientAdapter<Q, A>,一次是IClient<Q, A, ...>。所以ClientAdapter<Q, A, ...>不能隐式转换为IClient<Q, A>,因为转换会不明确。不确定您的继承是否是最好的方法,但要解决这个特殊问题,在任何地方继承virtual IClient<Q, A>和virtual IClient<Q, A, ...>就可以了。那么这个类也不应该是纯粹的了。 -
所以
IClient<A, B, C, D, E, F>继承了IClient<C, D, E, F>,但是为什么不继承IClient<A, B, C, D>呢?甚至IClient<A, B, E, F>。这种方法不能很好地扩展。可能没有继承,但是有一个类IClientAccessor<X1, X2, Y1, Y2, ...>,其构造函数模板采用IClient<...>,并自动检查客户端的参数列表是否是访问器参数的超集。如果是这样,它接受客户端。否则,它会拒绝客户端(不能从中转换)。否则它将像您的客户端适配器一样工作。 -
@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