我有一类带有发出请求的模板化方法的类。
这是类(class)
#include <iostream>
struct Client
{
template<class Request, class Response>
void sendRequest(const Request& q , Response& a)
{
std::cout << q << " " << a << "\n";
}
};
现在,发出未知类型的请求的方法很不寻常。大多数方法将发送一些请求类型,我希望这些方法使用接口(interface)以使代码可测试并清楚地表达它们对那些请求的依赖性。
但是我们都知道我们不能使模板化方法虚拟化。
因此,我想创建一个接口(interface),以将该类用于特定的请求和响应。
这里的界面
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;
};
这个想法是,有一个专门化需要两种参数类型,并为这两种类型定义
sendRequest
方法。我将模板参数推送到该类,因此该方法可以是虚拟的。
泛型类源自使用模板参数包的前两个参数创建的特殊化,因此我们可以定义多种请求和响应类型。
函数可以根据其需要执行的操作,例如
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);
}
但是我不想手动创建一个仅将 call 转发给客户端的类,我的时间比那更有值(value)。
让编译器为我努力!
这里的代码创建一个适配器,该适配器将客户端并遵守该接口(interface)。
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;
};
这次的想法是为给定用户使用的请求和响应类型的每个单个接口(interface)定义一个适配器,从它们派生出来,从而能够使用该类代替该接口(interface)。
梦想在这里破了。
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
接口(interface)中是纯函数,但我通过using Derived::method
将所有这些函数“取消隐藏”到类中,因此可以在调用ClientAdapter
时将其解析。我也想知道为什么该方法在错误中使用相同的参数出现2次。
如何获得此代码进行编译并做正确的事情?
这是一个wandbox示例,用于查看错误并尝试对其进行弄弄。
谢谢
最佳答案
简化的复制:
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 dominance(Live demo),问题就消失了。请注意,这种继承方式会在MSVC中引发警告C4250。您可以放心地忽略它。