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

时间:2017-08-08 21:39:57

标签: 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;
};

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

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

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

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

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

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

这里同样的问题适用于带指针的方法。此外,还有一个警告,即不能返回真正的无效对象。如果返回nullptr作为返回指针,则显然它是“无效”。但是,当涉及引用时,没有等同于nullptr

继续前进至第三位。按价值返回。

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

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

我可以继续...

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

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

3 个答案:

答案 0 :(得分: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]); 
}

用法:

CDriver x;
x.apply(3, [] (const COutput& o) {
    o.do_something();
});

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

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

答案 1 :(得分:0)

看看C ++ 11的共享指针。使用共享指针,在所有共享指针“拥有”该对象被销毁之前,不会调用基础对象的解构函数。在处理对单个对象的多个引用时,这会消除很多(但不是全部)头痛。

以下是一些更多信息: http://en.cppreference.com/w/cpp/memory/shared_ptr

答案 2 :(得分:0)

1)。经过试验和测试:抛出标准argument exception

2)您可以使用元组和std::tie

const std::tuple<bool, COutput> GetOutput(unsigned int idx) const 
{ 
     return idx < m_outputs.size() 
               ? std::make_tuple(true m_outputs[idx])
               : std::make_tuple(false,  m_invalidOutput); 
}

bool has_value;
COutput output;

std::tie(has_value, output) = GetOutput(3);

替换元组和C++17 structured bindings中的std :: tie可以使用。

3)对于这种情况,C ++ 17将有std::optional