【问题标题】:Do we have a better way of returning abstract classes in C++?我们有更好的方法在 C++ 中返回抽象类吗?
【发布时间】:2016-03-17 16:36:07
【问题描述】:

我想开始在我的 C++ 代码中添加一些接口,以便更轻松地使用 mock 进行单元测试。

这样做的问题是从 C++ 中的方法返回抽象类很痛苦。不能按值返回,所以需要返回指针或引用。

鉴于过去六七年 C++ 的所有发展,我想我想问一下我们是否有更好的方法来返回抽象基类。没有噪音的界面看起来像这样,但我确信这是不可能的。

IBaseInterface getThing() {return DerivedThing{};}

我记得过去这样做的方式是使用指针(现在可能是智能指针):

std::unique_ptr<IBaseInterface> getThing() {return std::make_unique<DerivedThing>();}

指针的问题在于,我实际上从未打算利用 nullptr,因此处理指针而不是值的开销和噪音对我作为读者没有任何价值。

我不知道有没有更好的方法来处理这个问题?

【问题讨论】:

  • 我不清楚为什么使用 mock 进行单元测试需要抽象接口。
  • 请注意,您的第一个示例不起作用,您需要返回指针或引用。
  • 在某些极端错误情况下,如果不计划exit,构建永远不会处于空状态的多态类型是非常困难的。如果您知道某些东西不是 nullptr,则可以不检查它:unique_ptr 的实际成本是它是堆分配的(在免费存储上)而不是自动存储。但是,如果您愿意限制对象的大小并在极端情况下承认罕见的exit,则发布接口(通过 vtable 或通过类型擦除)的智能联合类型可以工作,具有不同数量的工作。
  • 你不应该为了单元测试而牺牲你的软件架构。
  • @ChristianHackl 这是一个有争议的声明。这样做通常会改进架构。

标签: c++ interface c++17 return-value-optimization abstract-base-class


【解决方案1】:

编辑:提供完整的示例,包括使多态句柄可复制。

#include <iostream>
#include <utility>
#include <memory>

struct IBaseInterface {
    IBaseInterface() = default;
    IBaseInterface(IBaseInterface const&) = default;
    IBaseInterface(IBaseInterface &&) = default;
    IBaseInterface& operator=(IBaseInterface const&) = default;
    IBaseInterface& operator=(IBaseInterface &&) = default;
    virtual ~IBaseInterface() = default;

    virtual std::unique_ptr<IBaseInterface> clone() const = 0;
    virtual void do_thing() = 0;
};

struct handle
{
    handle(std::unique_ptr<IBaseInterface> ptr)
    : _impl(std::move(ptr))
    {}

    handle(handle const& r)
    : _impl(r._impl->clone())
    {}

    handle(handle && r)
    : _impl(std::move(r._impl))
    {}

    handle& operator=(handle const& r)
    {
        auto tmp = r;
        std::swap(_impl, tmp._impl);
        return *this;
    }

    handle& operator=(handle && r)
    {
        _impl = std::move(r._impl);
        return *this;
    }


    // interface here
    void do_thing() { _impl->do_thing(); }

private:
    std::unique_ptr<IBaseInterface> _impl;
};

struct DerivedThing : IBaseInterface
{
    std::unique_ptr<IBaseInterface> clone() const override
    {
        return std::make_unique<DerivedThing>(*this);
    }

    void do_thing() override
    {
        std::cout << "I'm doing something" << std::endl;
    }

};

handle make_thing()
{
    return handle(std::make_unique<DerivedThing>());
};

int main()
{
    auto a = make_thing();
    auto b = a;

    a.do_thing();
    b.do_thing();

    return 0;
}

现在使用您的句柄,就好像它具有(可移动的)值语义

【讨论】:

  • 是的,就是这个。可以将其扩展为使用clone() 方法来实现复制和分配(或实现value_ptr 并使用它。)
  • 当然只需要从复制操作符中调用clone()。不要忘记 clone() 应该返回一个 unique_ptr。
  • 我对这个解决方案的理解正确吗?:对于每个抽象基类B,必须定义一个具体的(即按值返回)类BHandle,它(a)包装了一个@987654326 @, (b) 与 unique_ptr 具有相同的复制/赋值语义,但 (c) 公开与 B 相同的接口,从而避免调用者取消引用它(即没有 *bHandlebHandle-&gt;...) .该接口是通过转发到包含的智能指针引用的对象来实现的。 (d) 复制构造是通过在B(“虚拟复制构造函数”模式)中使用clone 方法来实现的。
  • @stakx 差不多,是的。模板可以帮助处理样板工作量。
猜你喜欢
  • 1970-01-01
  • 2010-12-13
  • 2018-05-08
  • 2014-04-03
  • 2013-02-02
  • 2016-12-28
  • 2013-06-28
  • 1970-01-01
  • 2021-12-15
相关资源
最近更新 更多