我正在尝试模拟没有任何虚函数的类。我曾经读过Curiously_recurring_template_pattern(CRTP)可以帮助实现这一目标。
这是代码。我正在尝试对单元测试函数getMyClassValue
// file myclass.h
template<typename T>
struct MyClass_t {
int hello() {
return (static_cast<T*>(this))->hello_impl();
}
};
/*
Earlier MyClassImpl was just simple class like
struct MyClassImpl {
int hello() {
return 110;
}
};
// I changed it to below for making it mockable. Using CRTP.
*/
struct MyClassImpl : public MyClass_t<MyClassImpl> {
int hello_impl() {
return 110;
}
};
typedef MyClassImpl *MyClass;
int getMyClassValue(MyClass doc) {
return doc->hello();
}
// file main.cpp
#include <iostream>
/*
int main() {
MyClass myclass = new MyClassImpl();
std::cout << getMyClassValue(myclass);
delete myclass;
return 0;
}
*/
// file test.cpp
struct MyClassImplTest : public MyClass_t<MyClassImplTest>,
public virtual MyClassImpl {
int hello_impl() {
return 2;
}
};
int main() {
auto myclass = new MyClassImplTest();
std::cout << getMyClassValue(myclass);
delete myclass;
return 0;
}
我在控制台中收到110
而不是2
。
为什么会这样?由于我使用指针,切片不应该发生。
我如何实现模拟?
答案 0 :(得分:1)
实际上,这与切片无关。真正发生的事情非常简单:
getMyClassValue
接受MyClassImpl*
类型的指针并调用hello
hello
的父MyClassImpl
中的MyClass_t<MyClassImpl>
<{1}} hello
static将指针T*
投射到MyClassImpl*
并调用hello_impl
MyClassImpl::hello_impl
返回110 对hello
的调用无法解析为MyClass_t<MyClassImplTest>
的{{1}}父级,因为它在类型为MyClassImplTest
的指针上调用,而不是在{{1}类型的指针上调用}}。尝试在MyClassImpl*
上调用MyClassImplTest
将无效,除非您指定要使用哪个父项,因为调用不明确。
使用虚函数实现模拟很简单。没有,没有那么多。
答案 1 :(得分:0)
MyClass
是指向MyClassImpl
的指针。在其上调用hello
会调用MyClass_t<MyClassImpl>::hello
,将this
投射到MyClassImpl*
,从而调用MyClassImpl::hello_impl
。
如果您正在使用编译时“多态”(CRTP),则所有类型在编译时都必须正确。
我认为实现这项工作的唯一方法是让所有成为模板。您只需将实现类型作为模板参数/参数传递。 getMyClassValue
的一个例子是:
template <class T_MyClass>
int getMyClassValue(MyClass_t<T_MyClass> *doc)
{
return doc->hello();
}
struct MyClassImplTest : public MyClass_t<MyClassImplTest>
{
int hello_impl() {
return 2;
}
};
int main()
{
auto myclass = new MyClassImplTest();
std::cout << getMyClassValue(myclass);
delete myclass;
return 0;
}
这必须适用于所有生产代码和所有测试代码。通过将所有类型收集到“类型上下文”中可以使其更加可行,该类型上下文将存储每个接口的实现类型。然后将明确地传递它。
struct ProductionContext
{
typedef MyClassImpl MyClass;
typedef MyClass2Impl MyClass2;
};
struct Mock1Context
{
typedef MyClassImplTest MyClass;
typedef MyClass2Impl MyClass2;
};
struct Mock2Context
{
typedef MyClassImpl MyClass;
typedef MyClass2ImplTest MyClass2;
};
template <class T_Context>
int getMyClassValue(typename T_Context::MyClass *doc)
{
return doc->hello();
}
int main()
{
auto myclass = new MyClassImplTest();
std::cout << getMyClassValue<Mock1Context>(myclass);
delete myclass;
return 0;
}
同样,生产和测试代码的每一点都必须由T_Context
模板化。为了防止只需要包含头文件的所有内容,您可以使用您打算使用的所有上下文显式实例化模板。
对我来说,这会非常笨拙,但它是的解决方案。
旁注:请不要隐藏typedef后面的指针。 typedef MyClassImpl *MyClass;
是纯粹的混淆,永远不会通过代码审核。