创建C 类的存根版

Create a stub version of a C++ class

本文关键字:存根 创建      更新时间:2023-10-16

我想创建基于预处理定义的类的存根或模拟版本。当预处理器宏设置为"启用"时,该类只是正常类。设置为"禁用"时,该类是一个空的存根,编译器可以希望完全优化。但是,编译或与真实类不完整编译的代码也应具有与存根类的相应行为。

这是一个外观的示例:

class _foo {
 public:
    foo(int x) : x_(x) {}
    void add(int x) { x_ += x; }
    void add(const char *str) { x_ += atoi(str); }
    bool isset(void) { return x_ > 0; }
 private:
    int x_;
};
#if ENABLE_FOO
using foo = _foo;
#else
class foo {
 public:
    foo(int x) {}   
    void add(int x) { return; }
    void add(const char *str) { return; }
    bool isset(void) { return false; }
};
#endif

isset()的定义旨在允许像if (a_foo.isset()) { code(); }这样的代码将其优化为一无所有。显然,对于任何方法和任何使用该方法,这都不能普遍起作用。一个人需要设计课程,以使0,false,null等是在残疾情况下是明智的返回值。

这可以正常工作,但是必须将foo的存根版与真实版本完全同步。对任何方法的每一个更改都必须在存根中重复。这很烦人。如何使存根更自动?理想情况下,可以写class foo_stub : public stub<foo> {};STUB(foo),并且单独创建存根类。

为此,到目前为止,我已经能够提出这一点:

class foo {
 public:
    CTOR_STUB(_foo, foo);
    METHOD_STUB(_foo, add);
    METHOD_STUB(_foo, isset);
};

创建_foo的存根版本。确实需要列出每个方法名称,但是尚未提供返回类型,参数,也不需要提供参数数量。一个METHOD_STUB涵盖了所有过载(即两种add()方法(。过载可以具有不同的返回类型。如果固执方法是方法模板,它甚至可以工作。

这是这样做的宏:

#define METHOD_STUB(base, func) 
    template <typename... Args> 
    auto func(Args... args) { 
        using RetType = decltype(std::declval<base>().func(std::forward<Args>(args)...)); 
        return (RetType)0; }
#define CTOR_STUB(base, name) 
    template <typename... Args> 
    name(Args... args) { return; base _dummy{std::forward<Args>(args)...}; }

这个想法是定义一个模板,该模板要求使用适当的参数和返回类型的方法中存在,以正确地编译,但会由编译器进行优化。

有没有一种方法可以避免宏,只使用模板才能使用?似乎有人希望该方法的名称是模板参数,而我看不到该怎么做。

有没有办法避免需要在CTOR_STUB()中提供当前类的名称?编译器确实知道名称,但是我看不到一种将名称作为可以用来定义构造函数模板的符号的方法

是否有一些缺陷可以正确编译,或者当类的真实版本不执行相同的情况时,可以正确编译或无法编译?

不是一个完美的解决方案,但您可以做

#ifdef ENABLE
#define IF_ENABLED(x) x
#define IF_DISABLED(x)
#else
#define IF_ENABLED(x)
#define IF_DISABLED(x) x
#endif
class Foo {
public:
    foo(int x) IF_ENABLED(: x_(x)) {}
    void add(int x) { IF_ENABLED(x_ += x;) }
    void add(const char *str) { IF_ENABLED(x_ += atoi(str);) }
    bool isset(void) { IF_ENABLED(return x_ > 0;) IF_DISABLED(return false;) }
private:
#ifdef ENABLE
    int x_;
#endif
};

一个完全避免宏(从构建系统进入的东西(的好解决方案如下:

#if ENABLE_FOO
constexpr bool g_use_foo = true;
#else
constexpr bool g_use_foo = false;
#endif
template <bool FooEnabled>
struct Foo {
  void bar1() {}
};
template <>
void Foo<true>::bar1() { std::cerr << "not a mockn"; }
using UserFoo = Foo<g_use_foo>;

实时示例:http://coliru.stacked-crooked.com/a/ecdb7c1a7f0a6068

基本上,我们声明了一个模板类,并在内联给了通用的微不足道模拟实现。在身体外,我们定义了具有实际功能的班级专业化。在内线声明和不合时宜的声明必须完全匹配是一事无成的,否则您将专注于尚未声明的东西。因此,如果您更新一个但没有更新另一个,则会遇到编译时间错误。请注意,这很好地反映了一个典型的非模板类,其中您有声明和定义,所以我认为这很合理。

唯一无法涵盖的情况是,如果您删除或忘记实现了真实对象的方法。这仍然会编译,即使您认为自己正在使用真实对象,也只会获得模拟功能。也就是说,我不认为这是一个主要问题。在这种情况下,即使是最基本的一线单元测试也会失败。如果您为此担心,可以写:

template <bool FooEnabled>
struct Foo {
  void bar1() { static_assert(!FooEnabled, "");}
};

会抓住这个。

您也可以轻松涵盖状态。只是从具有状态的结构中私下继承:

template <bool FooEnabled>
struct Foo : private std::conditional_t<FooEnabled, FooState, EmptyStruct> {
    void bar1() {}
};

请注意,无论构建如何,这两个类都是完全定义的,并且可用于测试和工具。