繁体   English   中英

使用CRTP模拟类

[英]Using CRTP for mocking classes

我正在尝试模拟没有任何虚函数的类。 我读过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 为什么会这样呢? 由于我使用的是指针,因此不应进行切片。

我如何实现嘲讽?

确实,这与切片无关。 实际上,发生的事情非常简单:

  • getMyClassValue接受类型为MyClassImpl*的指针并调用hello
  • 调用在MyClassImpl的父MyClassImpl MyClass_t<MyClassImpl>解析为hello
  • hello static将指针转换为T* ,即MyClassImpl*并调用hello_impl
  • MyClassImpl::hello_impl返回110

要将呼叫hello不能解析到MyClass_t<MyClassImplTest>的母公司MyClassImplTest因为它呼吁类型的指针MyClassImpl*而不是与类型的指针MyClassImplTest 除非您指定要使用哪个父级,否则尝试在MyClassImplTest*上打个hello也将不起作用,因为该调用将是模棱两可的。

使用虚函数实现模拟将很简单。 没有,没有那么多。

MyClass是指向MyClassImpl的指针。 呼叫hello就可以调用MyClass_t<MyClassImpl>::hello ,这施放thisMyClassImpl* ,因此呼吁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进行模板T_Context 为了避免所有内容都只包含标头,您可以使用要使用的所有上下文显式实例化模板。

对我来说,这是令人难以置信的笨拙,但这一个解决方案。


旁注:请不要将指针隐藏在typedef后面。 typedef MyClassImpl *MyClass; 是纯粹的混淆, 永远不会和我一起通过代码审查。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM