2017-08-08 53 views
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应该能够授予列举COutput S中的用户访问。

实现这一目标的第一个方法是返回一个指针:

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

我看到它的方式,这种方法存在的问题是,如果指针由用户存储和它的CDriver对象后,仍然存在已经被摧毁,它现在是一个摇摆不定的指针。这是因为在CDriver对象的析构函数中指针对象(COutput对象)已被破坏。

这样做将是参考返回的第二种方式:

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 S和返回等。但是,这会使代码非常冗长。

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

+1

这取决于具体情况。针对不同情况的不同退货方式。我认为你需要更具体。 – Galik

+1

通过引用/值返回的两种可能的解决方案:1)抛出索引超出范围的异常(这是['std :: vector :: at'](http://en.cppreference.com/w/cpp/container/vector/at)does),或者2)具有未定义的行为来引发越界(这是['std :: vector :: operator []'](http://en.cppreference.com/w/cpp/container/vector/operator_at))。 – Frxstrem

+0

@Frxstrem但是这仍然不能解决对象必须被复制的问题 – Philinator

回答

5

让我开始说,你绝对不应该开始搞shared_ptr来解决这个问题。只是不要这样做。这里有几个不同的选项是合理的。

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

其次,您可以通过指针/引用返回。是的,它可以摇晃。但是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的多态性,你应该考虑这一点。

0

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

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

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

要更换的元组和std ::领带C++17 structured bindings可以使用。

3)对于这种场景,C++ 17将有std::optional

相关问题