【问题标题】:Why is it OK to return a 'vector' from a function?为什么可以从函数返回“向量”?
【发布时间】:2014-05-04 12:06:49
【问题描述】:

请考虑此代码。我已经多次看到这种类型的代码。 words 是一个局部向量。如何从函数中返回它?

我们能保证它不会死吗?

 std::vector<std::string> read_file(const std::string& path)
 {
    std::ifstream file("E:\\names.txt");

    if (!file.is_open())
    {
        std::cerr << "Unable to open file" << "\n";
        std::exit(-1);
    }

    std::vector<string> words;//this vector will be returned
    std::string token;

    while (std::getline(file, token, ','))
    {
        words.push_back(token);
    }

    return words;
}

【问题讨论】:

  • 返回时被复制。
  • 没有人保证......它死,但在被复制之后。
  • 只有在函数返回引用时才会出现问题:std::vector&lt;std::string&gt;&amp;
  • @songyuanyao 不会,会搬家的。
  • @songyuanyao 是的。 C++11 是当前的标准,所以 C++11 就是 C++。

标签: c++ vector stl scope standard-library


【解决方案1】:

C++11 前:

该函数不会返回局部变量,而是返回它的副本。但是,您的编译器可能会在没有进行实际复制操作的情况下执行优化。

更多详情请参阅this question & answer

C++11:

函数将移动值。详情请见this answer

【讨论】:

  • 它将被移动,而不是复制。这是有保证的。
  • 这是否也适用于 C++10?
  • 没有 C++10 这样的东西。
  • C++03 没有移动语义(但可能已经省略了复制),但 C++ 是 C++11,问题是关于 C++。
  • C++11 独有的问题有一个单独的标签。我们中的许多人,尤其是大公司的程序员,仍然坚持使用尚未完全支持 C++11 的编译器。我更新了问题,使其对这两个标准都准确。
【解决方案2】:

我们能保证它不会死吗?

只要没有返回引用,这样做就完全没问题。 words 将被移动到接收结果的变量中。

局部变量将超出范围。在移动(或复制)之后。

【讨论】:

  • 但是对于可能包含 1000 个条目的向量来说,效率高还是有任何性能问题?
  • @zadane 有问题吗?我还提到了 moving 这将避免实际获取返回值的副本(至少在当前标准下可用)。
  • 不,不是真正的问题,但我正在独立地从这个角度寻找答案。我不知道我是否发布了我的问题,恐怕他们会将其标记为重复 :)
  • @zadane “我担心他们会将其标记为重复” 很可能。看看higher voted answer。即使对于较旧的实现,您也不用担心,这些编译器无论如何都会正确优化它们。
【解决方案3】:

我认为您指的是 C(和 C++)中不允许从函数返回数组(或至少不会按预期工作)的问题 - 这是因为数组返回将(如果您写成简单的形式)返回一个指向堆栈上实际数组的指针,然后在函数返回时立即将其删除。

但在这种情况下,它可以工作,因为std::vector 是一个类,并且类,如结构,可以(并且将)复制到调用者上下文中。 [实际上,大多数编译器将使用称为“返回值优化”的东西来优化这种特定类型的副本,专门引入以避免在从函数返回时复制大对象,但这是一种优化,从程序员的角度来看,它将表现得好像为对象调用了赋值构造函数]

只要你不返回一个指针或对函数返回的东西的引用,你就可以了。

【讨论】:

    【解决方案4】:

    为了更好地理解行为,您可以运行以下代码:

    #include <iostream>
    
    class MyClass
    {
      public:
        MyClass() { std::cout << "run constructor MyClass::MyClass()" << std::endl; }
        ~MyClass() { std::cout << "run destructor MyClass::~MyClass()" << std::endl; }
        MyClass(const MyClass& x) { std::cout << "run copy constructor MyClass::MyClass(const MyClass&)" << std::endl; }
        MyClass& operator = (const MyClass& x) { std::cout << "run assignation MyClass::operator=(const MyClass&)" << std::endl; }
    };
    
    MyClass my_function()
    {
      std::cout << "run my_function()" << std::endl;
      MyClass a;
      std::cout << "my_function is going to return a..." << std::endl;
      return a;
    }
    
    int main(int argc, char** argv)
    {
      MyClass b = my_function();
    
      MyClass c;
      c = my_function();
    
      return 0;
    }
    

    输出如下:

    run my_function()
    run constructor MyClass::MyClass()
    my_function is going to return a...
    run constructor MyClass::MyClass()
    run my_function()
    run constructor MyClass::MyClass()
    my_function is going to return a...
    run assignation MyClass::operator=(const MyClass&)
    run destructor MyClass::~MyClass()
    run destructor MyClass::~MyClass()
    run destructor MyClass::~MyClass()
    

    请注意,此示例是在 C++03 上下文中提供的,它可以针对 C++ >= 11 进行改进

    【讨论】:

    • 这个例子如果包含一个移动构造函数和一个移动赋值运算符,而不仅仅是复制构造函数和复制赋值运算符,会更完整。 (如果移动功能不存在,将使用复制功能。)
    • @SomeGuy 我同意,但我不使用 C++11。我无法提供我没有的知识。我添加一个注释。随意添加 C++ >= 11 的答案。:-)
    【解决方案5】:

    我不同意并且不推荐返回vector

    vector <double> vectorial(vector <double> a, vector <double> b)
    {
        vector <double> c{ a[1] * b[2] - b[1] * a[2], -a[0] * b[2] + b[0] * a[2], a[0] * b[1] - b[0] * a[1] };
        return c;
    }
    

    这要快得多:

    void vectorial(vector <double> a, vector <double> b, vector <double> &c)
    {
        c[0] = a[1] * b[2] - b[1] * a[2]; c[1] = -a[0] * b[2] + b[0] * a[2]; c[2] = a[0] * b[1] - b[0] * a[1];
    }
    

    我在 Visual Studio 2017 上进行了测试,在发布模式下得到以下结果:

    8.01 参考文献
    5.09 MOPs 返回向量

    在调试模式下,情况更糟:

    0.053 MOPS 参考
    返回向量 0.034 MOPs

    【讨论】:

      【解决方案6】:

      这实际上是设计的失败。您不应该将返回值用于任何不是相对微不足道的东西的原始值。

      理想的解决方案应该通过返回参数来实现,该参数决定引用/指针并正确使用“const\'y\'ness”作为描述符。

      除此之外,您应该意识到 C 和 C++ 中数组上的标签实际上是一个指针,它的订阅实际上是一个偏移量或加法符号。

      因此返回 foo[offset] 的标签或 ptr array_ptr === 数组标签实际上是在内存指针位置 foo + 类型返回类型的偏移量处返回元素。

      【讨论】:

      • .......什么。很明显,您没有资格提出诸如“设计失败”之类的指责。事实上,RVO 和移动操作对值语义的提升是现代 C++ 风格的 主要成功之一。但是您似乎一直在思考原始数组和指针,所以我不希望您掌握这一点。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-18
      相关资源
      最近更新 更多