3

考虑以下场景:有一个类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应该能够授予用户对枚举COutputs 的访问权限。

实现这一点的第一种方法是返回一个指针:

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; 
}

这里的问题与使用指针的方法相同。此外,它还有一个额外的警告,即不能返回真正的无效对象。如果 anullptr作为返回指针返回,很明显它是“无效的”。nullptr但是,在引用方面没有等价物。

继续接近第三。按值返回。

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

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

我可以继续...

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

有什么办法可以直观地解决这个问题,并且不会引入不必要的代码冗长?

4

3 回答 3

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 的多态性,您应该考虑这一点。

于 2017-08-08T21:52:31.290 回答
0

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

这里有更多信息: http ://en.cppreference.com/w/cpp/memory/shared_ptr

于 2017-08-08T22:00:54.770 回答
0

1)。久经考验:抛出标准参数异常

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 结构化绑定替换元组和 std::tie 。

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

于 2017-08-08T22:33:06.630 回答