【问题标题】:Does returning a standard container incur a copy of the contents of the container?返回标准容器是否会产生容器内容的副本?
【发布时间】:2011-06-29 09:50:26
【问题描述】:

如果我有一个返回 STL 容器的函数,我是否会复制标准容器的全部内容?

例如这是:

void Foo( std::vector< std::string >* string_list );

比这更好:

std::vector< std::string > Foo();

容器里有什么重要吗?例如会返回一个这样的容器:

struct buzz {
    int a;
    char b;
    float c;
}

std::map< int, buzz > Foo();

比这更昂贵的操作:

std::map< int, int > Foo();

谢谢, 保罗H


编辑: 这是 C++03。不幸的是,C++0x 解决方案是不可接受的。

编辑2: 我正在使用 Microsoft Visual Studio 2008 编译器。

【问题讨论】:

  • 更重要的是,这有关系吗?不。将代码编写为 cleanstraightforward,然后当你完成后,你可以使用分析器来查看慢速部分是什么。如果从函数返回会占用大量时间,那么为了提高性能,混淆代码才是值得的。

标签: c++ optimization stl containers standard-library


【解决方案1】:

C++03 可能会做(命名)返回值优化(google RVO 和 NRVO)。

如果该优化不适用,C++0x 将执行move semantics

【讨论】:

    【解决方案2】:

    是的,它将涉及容器的副本,但不要使用void Foo( std::vector&lt; std::string &gt;* string_list );。请改用void foo( vector&lt;string&gt;&amp; string_list);

    或者只是切换到 C++0x 并使用已经在库中实现移动优化的编译器。

    【讨论】:

    • 它可能不会产生副本。事实上,在大多数情况下不会。这真的取决于函数的内容。
    • 为什么更喜欢引用语义?对于我打算在函数中修改的值,我一直很喜欢按指针传递的方法,这样用户在调用函数时就必须输入那个小&amp;。就像表示他们理解我的功能会改变价值一样。
    • 指针在语义上较弱,这将允许编译器忽略明确的代码错误。另一个大问题是通过指针传播 const(巨大的痛苦)。
    • @Paul:那么你的函数应该更好地命名。例如swap(x, y) 将修改xy,这并不令人困惑。
    【解决方案3】:

    我不是 100% 确定,但是没有(感谢评论员):

    #include <vector>
    #include <iostream>
    
    #define LOCAL_FUN
    
    struct A {
        A() { std::cout << "default ctor" << std::endl; }
        A(const A &a) { std::cout << "copy ctor" << std::endl; }
    };
    
    #ifdef LOCAL_FUN
    std::vector<A> *pVec = NULL;
    #endif
    
    std::vector<A> func()
    {
        std::vector<A> vec;
    #ifdef LOCAL_FUN
        pVec = &vec;
    #endif
        vec.push_back(A());
        std::cout << "returning" << std::endl;
        return vec;
    }
    
    int main(int argc, char *argv[])
    {
        std::vector<A> ret = func();
    #ifdef LOCAL_FUN
        if (pVec) {
            std::cout << pVec->size();
        }
    #endif
    }
    

    输出(带有 LOCAL_FUN):

    default ctor
    copy ctor
    returning
    1
    

    编辑:更多地使用代码让我对局部变量 (LOCAL_FUN) 产生了一些乐趣。所以一个不优化复制的非常糟糕的编译器实际上可以破坏这段代码......

    【讨论】:

    • 副本是在push_back制作的,返回时不是。实际上,您的代码显示在返回过程中 no 复制。
    • 哦,该死的,我本来打算写“不”——需要睡觉。
    • 请注意,尽管这是最可能的结果,但显示“copy ctor”两次(甚至三次)的输出也是正确的,并不意味着编译器损坏,只是错过了优化。跨度>
    • @ak 你可以用vec.emplace_back(); 代替vec.push_back(A()); 避免第一个(需要C++0x 支持)
    • @ak: std::vector&lt;A&gt; ret = func(); 可能涉及复制向量两次(因此复制了 A 的一个实例) - 一次从自动变量 vec 复制到临时变量,这是函数调用func(),另一个使用该临时函数初始化ret。这正是标准允许复制构造函数省略的两种情况,这并非巧合。
    【解决方案4】:

    首先编译器需要省略复制构造,如果不能,则移动,如果不能则复制。因此,如果您的编译器非常糟糕,则可能会产生额外副本的开销。详情请见this discussion

    【讨论】:

    • 没有要求删除副本;仅允许编译可能在某些情况下删除副本。
    • 不需要省略。甚至不需要实现首先“尝试”省略。
    • @James McNellis, @Fred Nurk -- 你可能错误地解析了我的回复。我不是说编译器需要省略,我只是让序列编译器在决定复制之前经过。
    • 这是一个正确的措辞,不应该引起混淆:当您按值传递右值,或从函数按值返回任何内容时,编译器首先获得省略副本的选项。如果副本没有被省略,但所讨论的类型具有移动构造函数,则编译器需要使用移动构造函数。最后,如果没有移动构造函数,编译器会回退到使用复制构造函数。
    【解决方案5】:

    这取决于容器的复制构造函数。 C++ 具有按值传递的语义。因此,当您为函数 Foo() 返回向量时,它将使用值语义返回,即将调用复制构造函数来复制向量的值。在这种情况下,std::vector 的复制构造函数创建一个新容器并复制值。如果将指针传递给容器,如果尚未分配内存,则必须分配内存,因此指针指向实际容器而不是空值。从编程实践的角度来看,这不是一件好事,因为您将语义留给解释。更好的办法是传递对容器的引用,然后让函数用所需的元素填充容器。

    【讨论】:

    • 这不是真的。请参阅 Fred 的答案,了解您的答案为何错误。
    猜你喜欢
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 2015-05-27
    • 2017-02-21
    • 1970-01-01
    • 1970-01-01
    • 2019-07-06
    • 1970-01-01
    相关资源
    最近更新 更多