C++ 在C++;,从函数返回向量仍然是一种不好的做法吗?

C++ 在C++;,从函数返回向量仍然是一种不好的做法吗?,c++,c++11,coding-style,return-value-optimization,C++,C++11,Coding Style,Return Value Optimization,简短版本:在许多编程语言中,返回大型对象(如向量/数组)是很常见的。如果C++类的C++类有一个移动构造函数,或者C++程序员认为它是怪异的/丑陋的/憎恶的,那么这种风格现在是可以接受的吗? 长版本:在C++0x中,这仍然被认为是错误的形式吗 std::vector<std::string> BuildLargeVector(); ... std::vector<std::string> v = BuildLargeVector(); std::vector Build

简短版本:在许多编程语言中,返回大型对象(如向量/数组)是很常见的。如果C++类的C++类有一个移动构造函数,或者C++程序员认为它是怪异的/丑陋的/憎恶的,那么这种风格现在是可以接受的吗? 长版本:在C++0x中,这仍然被认为是错误的形式吗

std::vector<std::string> BuildLargeVector();
...
std::vector<std::string> v = BuildLargeVector();
std::vector BuildLargeVector();
...
std::vector v=BuildLargeVector();
传统版本如下所示:

void BuildLargeVector(std::vector<std::string>& result);
...
std::vector<std::string> v;
BuildLargeVector(v);
String Something::id() const
{
    return valid() ? m_id: "";
}
void BuildLargeVector(std::vector&result);
...
std::向量v;
构建大向量(v);
在较新的版本中,从
BuildLargeVector
返回的值是一个右值,因此v将使用
std::vector
的移动构造函数构造,假设(N)RVO没有发生

甚至在C++0x之前,由于(N)RVO,第一种形式通常是“有效的”。但是,(N)RVO由编译器自行决定。现在我们有了右值引用,可以保证不会发生深度复制


编辑:问题其实不是关于优化。显示的两种形式在实际程序中的性能几乎相同。然而,在过去,第一种形式可能有数量级更差的性能。因此,第一种形式是C++编程中的一种主要代码气味。我希望现在不会了?

戴夫·艾布拉姆斯(Dave Abrahams)对这个问题有相当全面的分析


简短回答,如果需要返回值,则返回值。不要使用输出引用,因为编译器无论如何都会这样做。当然有一些警告,所以你应该阅读这篇文章。

如果性能是一个真正的问题,你应该意识到移动语义并不总是比复制快。例如,如果您有一个使用小字符串优化的字符串,那么对于小字符串,移动构造函数必须执行与常规复制构造函数完全相同的工作量。

只是要挑剔一点:在许多编程语言中,从函数返回数组并不常见。在大多数情况下,返回对数组的引用。在C++中,最接近的类比是返回代码< > Boo::SyddAlxAlx[/Cuth>

至少IMO,这通常是个糟糕的想法,但不是出于效率的原因。这是一个糟糕的想法,因为所讨论的函数通常应该作为一个通过迭代器生成其输出的通用算法来编写。几乎任何接受或返回容器而不是在迭代器上操作的代码都应该被认为是可疑的


不要误会我的观点:传递类似集合的对象(例如字符串)有时是有意义的,但是对于引用的例子,我会考虑传递或返回向量是一个很差的想法。

< P>我仍然认为这是一个坏的实践,但是值得注意的是,我的团队使用MSVC 2008和GCC 4.1,所以我们没有使用最新的编译器。 以前,使用MSVC 2008的vtune中显示的许多热点都归结为字符串复制。我们有这样的代码:

void BuildLargeVector(std::vector<std::string>& result);
...
std::vector<std::string> v;
BuildLargeVector(v);
String Something::id() const
{
    return valid() ? m_id: "";
}
。。。请注意,我们使用了自己的字符串类型(这是必需的,因为我们提供了一个软件开发工具包,插件编写器可以使用不同的编译器,从而使用不同的、不兼容的std::String/std::wstring实现)

为了响应调用图采样分析会话,我做了一个简单的更改,显示String::String(conststring&)占用了大量时间。上面示例中的方法是最大的贡献者(实际上,分析会话显示内存分配和释放是最大的热点之一,字符串复制构造函数是分配的主要贡献者)

我所做的改变很简单:

static String null_string;
const String& Something::id() const
{
    return valid() ? m_id: null_string;
}
然而,这带来了一个不同的世界!热点在随后的分析器会话中消失了,除此之外,我们还进行了大量彻底的单元测试,以跟踪我们的应用程序性能。在这些简单的更改之后,各种性能测试时间都显著减少

结论:我们没有使用绝对最新的编译器,但我们似乎仍然不能依赖编译器优化复制以可靠地返回值(至少不是在所有情况下)。对于那些使用MSVC 2010等较新编译器的人来说,情况可能并非如此。我期待着什么时候我们可以使用C++0x并简单地使用右值引用,而不必担心通过按值返回复杂类会使代码变得悲观

[编辑] 正如Nate指出的,RVO适用于返回函数内部创建的临时变量。在我的例子中,没有这样的临时变量(除了构造空字符串的无效分支),因此RVO不适用。

要点是:

复制省略和RVO可以避免“可怕的复制”(编译器不需要实现这些优化,在某些情况下无法应用)

C++0x右值引用允许一个字符串/向量实现保证这一点

如果您可以放弃旧的编译器/STL实现,那么可以自由返回向量(并确保您自己的对象也支持向量)。如果您的代码库需要支持“较小”的编译器,请坚持使用旧样式

不幸的是,这对您的接口有重大影响。如果C++ 0x不是选项,并且需要保证,那么在某些场景中,可以使用引用计数或复制写对象。不过,多线程也有缺点


(我希望C++中的一个答案是简单明了的,没有条件)。

< P>确实,自从C++ 11,复制<代码> STD::向量< /代码>的成本在大多数情况下消失了。 但是,应该记住,构造新向量(然后销毁它)的成本仍然存在,并且在需要时,使用输出参数而不是按值返回仍然很有用
size_t sum1 = 0;
for (int i = 0; i < numIter; ++i) {
    std::vector<int> v = BuildLargeVector1(vecSize);
    sum1 = std::accumulate(v.begin(), v.end(), sum1);
}
size_t sum2 = 0;
std::vector<int> v;
for (int i = 0; i < numIter; ++i) {
    BuildLargeVector2(/*out*/ v, vecSize);
    sum2 = std::accumulate(v.begin(), v.end(), sum2);
}
#include <chrono>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <vector>

class Timer {
    using clock = std::chrono::steady_clock;
    using seconds = std::chrono::duration<double>;
    clock::time_point t_;

public:
    void tic() { t_ = clock::now(); }
    double toc() const { return seconds(clock::now() - t_).count(); }
};

std::vector<int> BuildLargeVector1(size_t vecSize) {
    return std::vector<int>(vecSize, 1);
}

void BuildLargeVector2(/*out*/ std::vector<int>& v, size_t vecSize) {
    v.assign(vecSize, 1);
}

int main() {
    Timer t;

    size_t vecSize = size_t(1) << 31;
    size_t numIter = 1;

    std::cout << std::setw(10) << "vecSize" << ", "
              << std::setw(10) << "numIter" << ", "
              << std::setw(10) << "time1" << ", "
              << std::setw(10) << "time2" << ", "
              << std::setw(10) << "sum1" << ", "
              << std::setw(10) << "sum2" << "\n";

    while (vecSize > 0) {

        t.tic();
        size_t sum1 = 0;
        {
            for (int i = 0; i < numIter; ++i) {
                std::vector<int> v = BuildLargeVector1(vecSize);
                sum1 = std::accumulate(v.begin(), v.end(), sum1);
            }
        }
        double time1 = t.toc();

        t.tic();
        size_t sum2 = 0;
        {
            std::vector<int> v;
            for (int i = 0; i < numIter; ++i) {
                BuildLargeVector2(/*out*/ v, vecSize);
                sum2 = std::accumulate(v.begin(), v.end(), sum2);
            }
        } // deallocate v
        double time2 = t.toc();

        std::cout << std::setw(10) << vecSize << ", "
                  << std::setw(10) << numIter << ", "
                  << std::setw(10) << std::fixed << time1 << ", "
                  << std::setw(10) << std::fixed << time2 << ", "
                  << std::setw(10) << sum1 << ", "
                  << std::setw(10) << sum2 << "\n";

        vecSize /= 2;
        numIter *= 2;
    }

    return 0;
}
$ g++ -std=c++11 -O3 main.cpp && ./a.out
   vecSize,    numIter,      time1,      time2,       sum1,       sum2
2147483648,          1,   2.360384,   2.356355, 2147483648, 2147483648
1073741824,          2,   2.365807,   1.732609, 2147483648, 2147483648
 536870912,          4,   2.373231,   1.420104, 2147483648, 2147483648
 268435456,          8,   2.383480,   1.261789, 2147483648, 2147483648
 134217728,         16,   2.395904,   1.179340, 2147483648, 2147483648
  67108864,         32,   2.408513,   1.131662, 2147483648, 2147483648
  33554432,         64,   2.416114,   1.097719, 2147483648, 2147483648
  16777216,        128,   2.431061,   1.060238, 2147483648, 2147483648
   8388608,        256,   2.448200,   0.998743, 2147483648, 2147483648
   4194304,        512,   0.884540,   0.875196, 2147483648, 2147483648
   2097152,       1024,   0.712911,   0.716124, 2147483648, 2147483648
   1048576,       2048,   0.552157,   0.603028, 2147483648, 2147483648
    524288,       4096,   0.549749,   0.602881, 2147483648, 2147483648
    262144,       8192,   0.547767,   0.604248, 2147483648, 2147483648
    131072,      16384,   0.537548,   0.603802, 2147483648, 2147483648
     65536,      32768,   0.524037,   0.600768, 2147483648, 2147483648
     32768,      65536,   0.526727,   0.598521, 2147483648, 2147483648
     16384,     131072,   0.515227,   0.599254, 2147483648, 2147483648
      8192,     262144,   0.540541,   0.600642, 2147483648, 2147483648
      4096,     524288,   0.495638,   0.603396, 2147483648, 2147483648
      2048,    1048576,   0.512905,   0.609594, 2147483648, 2147483648
      1024,    2097152,   0.548257,   0.622393, 2147483648, 2147483648
       512,    4194304,   0.616906,   0.647442, 2147483648, 2147483648
       256,    8388608,   0.571628,   0.629563, 2147483648, 2147483648
       128,   16777216,   0.846666,   0.657051, 2147483648, 2147483648
        64,   33554432,   0.853286,   0.724897, 2147483648, 2147483648
        32,   67108864,   1.232520,   0.851337, 2147483648, 2147483648
        16,  134217728,   1.982755,   1.079628, 2147483648, 2147483648
         8,  268435456,   3.483588,   1.673199, 2147483648, 2147483648
         4,  536870912,   5.724022,   2.150334, 2147483648, 2147483648
         2, 1073741824,  10.285453,   3.583777, 2147483648, 2147483648
         1, 2147483648,  20.552860,   6.214054, 2147483648, 2147483648