我该如何从函数中返回一个对象?

Phi*_*tor 3 c++ pointers reference return-by-value

请考虑以下情形:有一个类CDriver负责枚举所有连接的输出设备(由COutput类表示).代码可能如下所示:

class COutput
{
    // COutput stuff
};

class CDriver
{
public:
    CDriver();  // enumerate outputs and store in m_outputs

    // some other methods

private:
    std::vector<COutput> m_outputs;
};
Run Code Online (Sandbox Code Playgroud)

现在CDriver应该能够授予用户访问枚举COutput的权限.

实现此目的的第一种方法是返回一个指针:

const COutput* GetOutput(unsigned int idx) const 
{ 
    return idx < m_outputs.size() ? &m_outputs[idx] : nullptr; 
}
Run Code Online (Sandbox Code Playgroud)

我看到它的方式,这个方法提出的问题是,如果指针由用户存储并且在CDriver对象被销毁后它仍然存在,那么它现在是一个悬空指针.这是因为在对象COutput的析构函数中,指针对象(对象)已被破坏CDriver.

第二种方式是通过引用返回:

const COutput& GetOutput(unsigned int idx) const 
{ 
    return idx < m_outputs.size() ? &m_outputs[idx] : m_invalidOutput; 
}
Run Code Online (Sandbox Code Playgroud)

这里同样的问题适用于带指针的方法.此外,还有一个警告,即不能返回真正的无效对象.如果a nullptr作为返回指针返回,则很明显它是"无效".然而,没有相当于nullptr参考的内容.

继续接近第三.按价值返回.

COutput GetOutput(unsigned int idx) const 
{ 
    return idx < m_outputs.size() ? &m_outputs[idx] : m_invalidOutput; 
}
Run Code Online (Sandbox Code Playgroud)

在这里,用户不必担心返回对象的生命周期.但是,COutput必须复制对象,并且与参考方法类似,没有直观的方法来检查错误.

我可以继续......

例如,COutput可以在堆上分配对象并将其存储在std::shared_ptrs中并按原样返回.然而,这会使代码非常冗长.

有没有办法直观地解决这个问题,而不会引入不必要的代码冗长?

Nir*_*man 5

让我首先说,你绝对不应该开始搞乱shared_ptr解决这个问题.只是不要这样做.这里有一些合理的选择.

首先,您可以简单地按价值返回.如果COutput很小,这是一个很好的方法.要处理越界索引,您有两个选择.一个是抛出异常.效果很好,很容易.这是我最有可能推荐的.确保有一个size()用户可以调用的成员来获得大小,这样他们可以避免支付投掷费用,如果这对他们来说太贵了.你也可以退货optional.这是在17之前的标准库中,在boost之前,并且有独立的实现.

其次,您可以通过指针/引用返回.是的,它可以摇摆不定.但C++并没有声称可以提供针对此的保护.每个返回迭代器的标准容器功能begin()end()方法都可以轻松摇晃.期待客户避免这些陷阱在C++中并不是不合理的(你当然应该记录它们).

第三,您可以进行控制反转:而不是让用户操作对象,而是让用户传递他们想要采取的操作.换一种说法:

template <class F>
auto apply(std::size_t idx, F f) const 
{
    if (idx >= m_outputs.size())
    {
        throw std::out_of_range("index is out of range");
    }

    return f(m_outputs[idx]); 
}
Run Code Online (Sandbox Code Playgroud)

用法:

CDriver x;
x.apply(3, [] (const COutput& o) {
    o.do_something();
});
Run Code Online (Sandbox Code Playgroud)

在这种情况下,用户需要更加努力地工作(尽管它仍然可能),因为它们没有传递指针/引用,并且您也不必复制.

你当然可以通过apply多种方式改变; 例如,不从函数调用返回,而是返回true/false以指示索引是否在范围内而不是抛出.基本思路是一样的.请注意,必须修改此方法以与虚函数结合使用,这将使其不太理想.因此,如果您正在考虑CDriver的多态性,您应该考虑这一点.