参考或返回 - 最佳实践

时间:2011-11-12 15:47:00

标签: c++ function optimization return-value-optimization

例如我们有编码功能。使用的最佳做法是什么:

void Crypto::encoding(string &input, string &output)
{
    //encoding string
    output = encoded_string;
}

string Crypto::encoding(string &input)
{
    //encoding string
    return encoded_string;
}

我们应该使用引用还是返回来返回字符串?据我所知,返回一个字符串将需要一些时间来初始化一个将由return指令返回的新字符串。在处理引用的变量时,我不会浪费时间来初始化一些新的变量,我只是结束了这个函数。

我们应该主要使用reference和make函数返回类型void吗?或者,当我们想要返回两个或多个变量时,我们应该只通过引用返回数据,当我们需要返回一个变量然后使用return指令时?

7 个答案:

答案 0 :(得分:10)

不要优化你没有测量的东西。

通常使用return返回计算结果会更好(更具可读性)。如果由于对象太胖而需要很长时间,您仍然可以恢复通过参考参数返回结果,但只有在您证明这将导致性能显着提高(测量它)之后。例如,如果您只编码非常短的字符串并且偶尔只编码一次,那么复制的开销可以忽略不计。

答案 1 :(得分:7)

由于大多数现代编译器都具有RVO功能,因此通常会删除复制内容。即使没有c ++ 11,你也可以获益。

答案 2 :(得分:3)

如果您的编译器支持C ++ 11标准和r-value references,则按值返回std :: string实际上非常有效。在此功能之前,答案可能会有所不同,因为您只依赖于执行RVO的编译器。

我想说使用返回值可能更自然,也意味着您可以将结果分配给常量局部变量或类成员以避免意外修改,例如。

const std::string result = crypo.encoding("blah");

或者

class SomeClass
{
public:
    Someclass(Crypto& crypto, const std::string& input) :
        m_output(crypo.encoding(input))
    {
    }

private:
    const std::string m_output;
};

确保按const值返回,因为这会抑制移动语义。

答案 3 :(得分:2)

我使用引用。这允许实现者做出并抽象出选择,而不会对客户造成沉重的负担(有些情况很重要,有些则不会)。

我也使用它们来保持一致的风格 - 我不喜欢看到通过实现细节的公共接口

瞬态和副本可能很昂贵 - 它会因您传递的类型而有很大差异。按值返回表示类型应该是易于构造,可交换,可复制,可移动的。编译器可以在此区域(RVO / move)中进行一些很好的优化,但您也可以做出明智的决策,以最大限度地减少实现中的昂贵操作。一旦你不再使用类型,每个人都知道复制特征,那么选择返回的方式变得非常复杂,所以我只是保持简单并且有利于引用。

传递引用还有一些其他好处,例如当客户端更喜欢使用传递类型的子类时。

如果您需要优化程序,另一个好处是:如果它们不是微不足道或可能的话,我会经常删除副本ctor和operator=。通过可变引用传递允许您使用不可复制/可分配的类型。

在此问题中使用的std::string的严格范围内:按值返回std::string非常常见,并且已针对此案例进行了许多优化 - RVO,COW和移动是一些值得注意的。正如Voo在下面的评论中提到的那样,按值返回通常更容易阅读。对于std::string和更高级别的程序,按值返回不太可能是一个问题,但是如果性能很重要,则需要进行测量以了解所使用的标准库实现所涉及的成本(您的问题可能就是这种情况。)

一个重要的考虑因素是,如果您正在尝试改进现有程序,请确保了解实施的执行方式,并了解在性能如何重要时如何最有效地使用这些类型。可以为实际使用编写和优化实现,这意味着它们可能是悲观的,并且在某些情况下再次猜测您,并且您可能已经实现了提高性能的尝试或者非常规使用该类型< em>降低性能。 std::vector的典型调整大小行为就是一个明显的例子。采用高性能道路确实会增加大量时间和复杂性,以达到最佳效果,这显然会因您使用的实施方式以及您使用的类型而异。如果

我还应该补充一点,我经常在低级别工作 - 性能至关重要和/或资源有限。可以有许多限制,包括没有异常,没有锁(也意味着没有堆分配),最小的抽象成本,甚至限制使用动态多态。即使对于C ++,它也可以被认为是一个相当苛刻的领域。我选择核心低级别部分的参考,但如果我知道某个程序仅用于更高级别的域或单元测试,我将放宽该规​​则。

答案 4 :(得分:1)

使用新的标准C ++ 11,您可以使用第二个变体,因为新的移动语义。

但最有可能的是,您的编译器仍然只支持较旧的标准。 在这种情况下,你的第一个例子不会引起任何复制,而且更好。

答案 5 :(得分:1)

我会记录下来:可能都不是。

你的encode看起来很像它可能/应该是一个通用算法,应该真正使用迭代器而不是直接处理字符串。

template <class InputIterator, class OutputIterator>
void encode(InputIterator begin, InputIterator end, OutputIterator result) {
    while (begin!=end)
        *result++ = encode_byte(*begin++);
}

通过这种方式,您可以(例如)轻松地重复使用完全相同的代码,将数据直接从输入流(通过std::istream_iterator)编码到输出流(通过std::ostream_iterator)。

这通常也会消除大多数关于效率的问题。

答案 6 :(得分:1)

我更喜欢第二个版本,因为它看起来更像是一个数学函数。如果你只是回归弦乐,你应该表现得很好。