参考或返回-最佳实践

reference or return - best practice

本文关键字:最佳 返回 参考      更新时间:2023-10-16

例如,我们有编码函数。最佳实践是什么:

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

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

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

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

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

通常用return返回计算结果会更好(更可读)。如果因为对象太大而花费了很长时间,您仍然可以通过引用参数返回结果,但只有在您证明这将导致性能的显着改进之后(测量它)。例如,如果你只编码非常短的字符串,并且只在一段时间内这样做,那么复制的开销可以忽略不计。

由于大多数现代编译器都具有RVO特性,因此通常会消除复制内容。即使没有c++11,您也可以从中获益。

如果您的编译器支持c++ 11标准和r值引用,那么按值返回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值返回,因为这会抑制move语义。

我使用引用。这允许实现者做出和抽象选择,而不会给客户端带来沉重的负担(有些情况很重要,有些情况不会)。

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

瞬态和副本可能是昂贵的——它因传递的类型而有很大差异。按值返回表明类型应该是可构造的、可互换的、可复制的、可移动的。编译器可以在这方面做一些很好的优化(RVO/move),但是您也可以做出明智的决定来最小化实现中昂贵的操作。一旦您不再使用所有人都知道其复制特征的类型,那么选择如何返回就会变得非常复杂,因此我只保持简单并使用引用。

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

如果您需要一个优化的程序,另一个好处是:我经常删除复制因子和operator=,如果它们不是微不足道的或可能的。通过可变引用传递可以使用不可复制/赋值的类型。

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

一个重要的考虑是,如果您试图改进现有的程序,请确保您了解实现是如何执行的,并了解如何在性能很重要的情况下最有效地使用类型。实现可能是为的实际使用而编写和优化的,这意味着它们在某些情况下可能是悲观的,并且会对您进行事后猜测,并且您改进性能的尝试可能已经实现,或者非常规地使用该类型可能会降低的性能。std::vector的典型调整大小行为就是一个明显的例子。走高性能之路确实会增加大量的时间和复杂性,因为要获得最佳结果需要了解哪些内容,这显然会因所使用的实现和所使用的类型而异。如果性能是至关重要的,并且值得投入大量的时间,那么学习使用的类型的操作是一项值得的努力,可以带来显著的收益。

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

在新的标准c++ 11中,由于新的move语义,您可以使用第二个变体。

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

我郑重声明:可能两者都不是。

你的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)编码数据。

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

我更喜欢第二个版本,因为它看起来更像一个数学函数。如果你只返回字符串,你应该有良好的性能。