【问题标题】:Google mock with templates and inheritance带有模板和继承的 Google 模拟
【发布时间】:2021-09-16 06:57:14
【问题描述】:

我目前正在尝试让模板和继承与 GMock 很好地配合。我有一种感觉,我想做的是两种相反的意识形态,我应该只使用一个接口,但我想避免使用接口,因为可能会产生虚拟调用开销(也许我过早地优化了)

无论如何,这是我正在尝试做的一个示例

class ConcreteObj {
public:
    // Called a lot and so don't want to hit possible virtual overhead
    void performant_function();
};

class MockObj {
public:
    MOCK_METHOD(void, performant_function, (), ());
};

class ITest {
public:
    template<typename T>
    void test_function(T& );
};

class ConcreteTest : public ITest {
public:
    template<typename T>
    void test_function(T& ) {};

    template<>
    void test_function<ConcreteObj>(ConcreteObj& obj) {
        // Do something with concrete object
        obj.performant_function();
    }

    template<>
    void test_function<MockObj>(MockObj& obj) {
        // Do something with mock object
    }
}

然后我想做的事情是这样的

ConcreteTest concrete_test;
ITest* test = &concrete_test;

// In production
ConcreteObj concrete_obj;
test.test_function(concrete_obj);

// In test
MockObj mock_obj;
test.test_function(mock_obj);

然后它会通过ITest 接口调用ConcreteTest,但是如果没有对ConcreteTest 的某种强制转换,上述方法显然无法工作,因为您不能拥有虚拟模板函数。

如果有人对我如何执行以下操作有任何想法,我将不胜感激,尽管我可能会放弃使用纯虚拟接口并处理如果有IObj 接口可能会出现的 vtable 开销ConcreteObjMockObj 继承自。

【问题讨论】:

  • 在这里真正拥有蛋糕并吃掉它的最佳方法是将依赖于 ConcreteObj 的代码模板化为该类型。
  • 另外:“(也许我过早地优化了)”如果你甚至对此感到疑惑,那么答案几乎肯定是肯定的。
  • ConcreteTest concrete_test; ITest* test = &amp;concrete_test; 的目的是什么?你想考哪一门课?
  • 我想测试 ConcreteTest,同时能够在 MockObj 和 ConcreteObj 上没有接口的情况下注入 MockObj
  • 我看不出class ITest 的意义。删除它,你已经可以做concrete_test.test_function(concrete_obj);/concrete_test.test_function(concrete_obj);

标签: c++ templates inheritance googlemock


【解决方案1】:

为了避免运行时多态,可以使用模板,如下:

class ConcreteObj {
public:
    // Called a lot and so don't want to hit possible virtual overhead
    void performant_function();
};

class MockObj {
public:
    MOCK_METHOD(void, performant_function, (), ());
};

class ITest {
public:
    virtual ~ITest() = default;
    virtual void test_function() = 0;
};

template <typename T>
// requires (T t){ t.performant_function(); } // C++20
class ConcreteTest : public ITest {
    T t;
public:
    explicit ConcreteTest(T& t) : t(t) {}
    void test_function()
    {
        for (int i = 0; i != big_number; ++i) {
           t.performant_function();
        }
        // ...
    }
};

然后

// In production
ConcreteObj concrete_obj;
ConcreteTest<ConcreteObj> concrete_test{concrete_obj};
ITest* test = &concrete_test;

test->test_function();
// In test
MockObj mock_obj;
// EXPECT_CALL(..)
ConcreteTest<MockObj > concrete_test{mock_obj};
ITest* test = &concrete_test;

test->test_function();

【讨论】:

  • 太棒了!一个漂亮的方法!感谢您的回答!
【解决方案2】:

如果您考虑为ConcreteObj 指定的要求,您需要做两件事:

  • performant_function() 方法的每次调用开销绝对最小。
  • 能够根据上下文(即运行测试与生产时)交换不同的实现。

只有一种保证方法可以同时获取两者:模板

您发布的代码没有提供很多上下文,所以它可能不会那么简单,但它看起来很像这样:

class ConcreteTest : public ITest {
public:
    template<typename T>
    void test_function(T& obj) {
       // Do something with obj
       obj.performant_function();
    };
};

// ...
ConcreteTest concrete_test;
ITest* test = &concrete_test;

// In production
ConcreteObj concrete_obj;
test->test_function(concrete_obj);

// In test
MockObj mock_obj;
test->test_function(mock_obj);

但是,你问:

(也许我过早地优化了)

答案几乎是肯定的。编译器非常擅长优化东西。在您的场景中,您可以使用 -flto 进行编译并使用:

class IObj {
  public: 
    virtual void performant_function() = 0;
};

class ConcreteObj final : public IObj {
  public: 
    virtual void performant_function() = 0;
};

这将很有可能在称为去虚拟化的优化过程中消除开销。

【讨论】:

  • 我认为 OP 问题是ConcreteTest concrete_test; ITest* test = &amp;concrete_test; 部分。
  • @Jarod42 我把那部分读作一个一次性的例子。从文本中,我似乎很清楚 OP 通常要求零成本的依赖注入。
  • @Jarod42 - 这是主要问题。因为通过接口调用会很好。我有一种感觉,除非我进行一些肮脏的铸造,否则我正在尝试做的事情是行不通的
  • @Frank - 如果我能给出两个答案,我也会勾选这个!您为我指出了正确的方向,因为我可能过早地进行了优化!非常感谢!
猜你喜欢
  • 2017-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-02
  • 1970-01-01
相关资源
最近更新 更多