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中并按原样返回.然而,这会使代码非常冗长.
有没有办法直观地解决这个问题,而不会引入不必要的代码冗长?
让我首先说,你绝对不应该开始搞乱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的多态性,您应该考虑这一点.