【问题标题】:How to pass class with virtual methods around and how to use it as member variable?如何使用虚拟方法传递类以及如何将其用作成员变量?
【发布时间】:2016-10-07 13:15:50
【问题描述】:

我最近开始使用 Catch 和 Fakeit 对我的代码进行单元测试。 我有一个围绕 WinAPIs HWND 的包装类。

class Window
{
public:
    Window(HWND hwnd);
    virtual void resize(int width, int height);
    ...
private:
    HWND m_hwnd;
};

这和它的测试工作正常。对于测试,我正在创建一些实际的 windows 使用 WinAPI 的 CreateWindow(...)。

但是,我偶然发现了一个问题,我不确定最佳解决方案是什么。 在我的代码中,我只是按值传递 Window,因为它基本上只是 HWND。 有些方法把它当作const&,但是当它作为类成员时,我通常只是复制它。

class Foo
{
public:
    Foo(const Window& window)
    : m_window(window)
    {}
private:
    Window m_window;
};

现在假设我想测试 Foo。我需要以某种方式存根 Window 类,但我只能这样做 如果我能够覆盖虚拟方法。我想你在这里看到了问题。

class Foo
{
public:
    Foo(const std::shared_ptr<Window>& window)
    : m_window(window)
    {
        assert(m_window != nullptr);
    }
private:
    std::shared_ptr<Window> m_window;
};

我现在花了最后两个小时,重构我所有的代码,所以我只通过 std::shared_ptr 和几乎 永远不要按值使用 Window 类。 Foo 现在看起来像这样: 班级 这是有道理的,因为即使我按值传递 Window,它通常也是共享的 无论如何(如果我调整它的大小,它会为所有实例调整大小)。 但是,我也觉得它使我的代码复杂了很多。对于比较运算符 == 我现在总是要取消引用 双方。当我试图在 stl 容器中查找时,我现在必须使用

std::find_if(haystack.begin(), haystack.end(),[&needle](const SharedWindowPtr& ptr) { return *ptr == *needle; });

除此之外,还有来自 shared_ptr 的开销。我还冒着传递 nullptrs 的风险,我想确保不会发生这种情况。我可以用断言来检查,但这并不是万无一失的。我也不能传递 shared_ptr 而是传递对 Foo 的构造函数的引用,并为 Window 类提供一个 std::unique_ptr&lt;Window&gt; clone() 方法,但我必须为每个测试模拟它,如果有办法解决它,我更愿意这样做。

现在我想知道是否有更好/更清洁的方法来处理此类问题?感谢您对此提出任何建议。

编辑:在考虑了一段时间后,我有了另一个想法来解决这个问题。

class WindowHandler
{
public:
    virtual void resize(int w, int h) = 0;
    virtual void getTitle() = 0;
    // ...
};

class DefaultWindowHandler
{
public:
    DefaultWindowHandler(HWND hwnd);
    virtual void resize(int w, int h) override
    {
        // ...
    }
    // ...
protected:
    HWND m_hwnd;
};

class Window
{
public:
    Window(HWND hwnd)
    : m_windowHandler(new DefaultWindowHandler(hwnd))
    {
        // empty
    }
    void setWindowHandler(WindowHandler* handler)
    {
        assert(handler != nullptr);
        m_windowHandler.reset(handler);
    }
    void resize(int w, int h)
    {
        m_windowHandler->resize(w, h);
    }
};

我喜欢这个的一点是,我可以通过值传递 Window 类,并且如果需要的话,仍然可以获得类似接口的行为的好处。对此你怎么看,有什么不足之处?我知道 Window 可能应该接受 WindowHandler 而不是 HWND 作为构造函数参数,但使用它的方式更容易。

【问题讨论】:

  • 只要Foo 不是Window 的所有者,简单的引用或非拥有的原始指针都可以。如果您需要Foo 成为所有者,您可以使用std::unique_ptr
  • 我知道我当然可以使用 unique_ptr ,但这并不能简化任何事情,如果这会让事情变得更复杂的话。我也知道我可以使用原始指针,但在大多数情况下我没有特定的所有者,但窗口是从多个地方操作的。感谢您的评论。
  • 您能否详细说明这里涉及的类层次结构?还是只是那一类?还有很多其他的成员函数吗?

标签: c++ unit-testing c++11 interface smart-pointers


【解决方案1】:

按照评论中的要求,std::unique_ptr 似乎也会受到此选项的影响。如果基类的值,使用任何指针或引用不可用,您总是可以使用模板:

template<typename WindowType>
struct Foo {
    // Want speed? take by value!
    Foo(WindowType window)
    : m_window(std::move(window))
    {}

private:
    WindowType m_window;
};

然后,您的类将使用窗口的所有子类型。但是,使用模板,函数是否为虚拟是无关紧要的。

如果你想限制你的类 Foo 只接受窗口的子类型,你有两个选择:sfinae-like 或 static_assert

类似sfinae的

template<typename, typename = void>
struct Foo;

template<typename WindowType>
struct Foo<WindowType, std::enable_if_t<std::is_base_of<Window, WindowType>::value>> {
    Foo(WindowType window)
    : m_window(std::move(window))
    {}

private:
    WindowType m_window;
};

static_assert

template<typename WindowType>
struct Foo {
    static_assert(std::is_base_of<Window, WindowType>::value, "WindowType must be a subclass of Window");

    Foo(WindowType window)
    : m_window(std::move(window))
    {}

private:
    WindowType m_window;
};

类 sfinae 方法的优点是能够为与其他条件匹配的其他类型“重载”您的类,但 static_assert 更容易实现。

【讨论】:

  • 非常感谢。我现在花了一些时间重新考虑这一点。我什至没有考虑过这个模板。但是,这不适用于 fakeit(这是切换到类似接口的方法的主要原因),而我必须为每个测试进行子类化。虽然我不得不在主帖中添加另一个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多