如何使用虚方法传递类以及如何将其用作成员变量?

时间:2016-10-07 13:15:51

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

我最近开始使用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,它通常也是共享的 无论如何(如果我调整它的大小,它会针对所有实例调整大小)。 但是,我也觉得我的代码很复杂。对于比较运算符==我现在总是需要derefence 双方。当我试图在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<Window> clone()方法,但我必须为每个测试模拟它,如果有办法解决它,我宁愿这样做。 / p>

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

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

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作为构造函数参数,但它更容易使用它的方式。

1 个答案:

答案 0 :(得分: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只接受windows的子类型,你有两个选择: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的方法将具有能够超载&#34;您的类适用于与其他条件匹配的其他类型,但static_assert稍微容易实现。