【问题标题】:Why the move constructor doesn't get invoked in this case?为什么在这种情况下不调用移动构造函数?
【发布时间】:2014-10-29 13:42:04
【问题描述】:

我正在关注这篇文章Ten C++11 Features Every C++ Developer Should Use 并在Move semantics 示例的代码中添加了一些基本跟踪,并看到移动构造函数从未被调用,我想知道为什么。我尝试过使用 GNU 4.6.3 和 Intel 15.0.0 两种编译器,结果是一样的。

我是这样编译的:

# using Intel compiler
icpc -Wall -g -Wno-shadow -std=c++0x -o showcase ./showcase.cpp

# using gnu g++ compiler
g++ -Wall -g -Wno-shadow -std=gnu++0x -o showcase ./showcase.cpp

这是我在第 133 行未调用移动构造函数时得到的输出:

instantiating b1 ...
Buffer() default constructor invoked 
my name is: 
instantiating b2 ...
Buffer(const std::string& name, size_t size) constructor invoked 
my name is: buf2
instantiating b3 ...
Buffer(const Buffer& copy) copy constructor invoked 
my name is: buf2
instantiating b4 ...
Buffer(const std::string& name, size_t size) constructor invoked 
my name is: buf64
moving getBuffer<int>("buf5") to b1 ...
Buffer(const std::string& name, size_t size) constructor invoked 
Buffer& operator=(Buffer&& temp) move assignment operator invoked
my name is: buf5

代码如下:

#include <assert.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>

#include <map>
#include <vector>
#include <memory>
#include <algorithm>

using namespace std;

//============================================================================
// Classes
//============================================================================

template <typename T>
class Buffer 
{
   std::string          _name;
   size_t               _size;
   std::unique_ptr<T[]> _buffer;

public:
   // default constructor
   Buffer():
      _size(16),
      _buffer(new T[16]) {
      cout << "Buffer() default constructor invoked " << endl;
   }

   // constructor
   Buffer(const std::string& name, size_t size):
      _name(name),
      _size(size),
      _buffer(new T[size]) {
      cout << "Buffer(const std::string& name, size_t size) constructor invoked " << endl;
   }

   // copy constructor
   Buffer(const Buffer& copy):
      _name(copy._name),
      _size(copy._size),
      _buffer(new T[copy._size])
   {
      cout << "Buffer(const Buffer& copy) copy constructor invoked " << endl;
      T* source = copy._buffer.get();
      T* dest = _buffer.get();
      std::copy(source, source + copy._size, dest);
   }

   void print_name() const {
        cout << "my name is: " << _name << endl;
   }

   // copy assignment operator
   Buffer& operator=(const Buffer& copy)
   {
      cout << "Buffer& operator=(const Buffer& copy) assignment operator invoked " << endl;
      if(this != &copy)
      {
         _name = copy._name;

         if(_size != copy._size)
         {
            _buffer = nullptr;
            _size = copy._size;
            _buffer = _size > 0 ? new T[_size] : nullptr;
         }

         T* source = copy._buffer.get();
         T* dest = _buffer.get();
         std::copy(source, source + copy._size, dest);
      }

      return *this;
   }

   // move constructor
   Buffer(Buffer&& temp):
      _name(std::move(temp._name)),
      _size(temp._size),
      _buffer(std::move(temp._buffer))
   {
      cout << "Buffer(Buffer&& temp) move constructor invoked" << endl;
      temp._buffer = nullptr;
      temp._size = 0;
   }

   // move assignment operator
   Buffer& operator=(Buffer&& temp)
   {
      cout << "Buffer& operator=(Buffer&& temp) move assignment operator invoked" << endl;
      assert(this != &temp); // assert if this is not a temporary

      _buffer = nullptr;
      _size = temp._size;
      _buffer = std::move(temp._buffer);

      _name = std::move(temp._name);

      temp._buffer = nullptr;
      temp._size = 0;

      return *this;
   }
};

template <typename T>
Buffer<T> getBuffer(const std::string& name) {
   Buffer<T> b(name, 128);
   return b;
}

//============================================================================
// Main
//============================================================================

int main(int argc, char** argv) {
    cout << "**************** move semantics" << endl;
    cout << "instantiating b1 ..." << endl;
    Buffer<int> b1;
    b1.print_name();
    cout << "instantiating b2 ..." << endl;
    Buffer<int> b2("buf2", 64);
    b2.print_name();
    cout << "instantiating b3 ..." << endl;
    Buffer<int> b3 = b2;
    b3.print_name();
    cout << "instantiating b4 by moving from a temp object ..." << endl;
    Buffer<int> b4 = getBuffer<int>("buf64"); // Buffer<int>("buf4", 64);
    b4.print_name();
    cout << "moving getBuffer<int>(\"buf5\") to b1 ..." << endl;
    b1 = getBuffer<int>("buf5");
    b1.print_name();

    return EXIT_SUCCESS;
}

【问题讨论】:

  • 我猜你看不到移动,因为发生了复制省略
  • 对于 GCC 使用 -fno-elide-constructors 命令行选项
  • 是的,使用命令行选项-fno-elide-constructors 解决了这个问题。我必须为 icpc 编译器找到相同的...
  • 你可以大大减少这个程序。

标签: c++ c++11 g++ icc


【解决方案1】:

正确调用了移动赋值运算符。

对于您期望移动构造的情况,b4,您将获得返回值优化 (RVO),其中结果对象直接在调用者提供的存储中构造。这是否发生取决于编译器和选项:允许但不是必需的。 IE。这是一个实施质量问题。


请注意,使用例如-fno-elide-constructors 避免这种情况。 RVO 比普通施工加移动施工效率高得多。它必须是,因为它更少。

【讨论】:

  • 感谢您的回答。我只是担心 RVO 没有发生。除了使用这个 no-elide-constructor 之外,有没有办法知道它是否正在发生?
  • @LightnessRacesinOrbit:使用 g++ 使用 -S 生成程序集。如果您更喜欢 Intel 语法(我愿意),在 PC 平台上可能使用-masm=intel。搜索相关名称。另一种选择(仍然适用于 g++)是在例如使用调试器。代码::块。
  • 不,你不是唯一的人。我拒绝阅读程序集以了解是否有什么工作:)
【解决方案2】:

在某些情况下允许省略复制/移动操作。虽然复制或移动构造函数是可以接受的。例如,如果您将在您的类中为移动构造函数设置私有访问控制,那么编译器将至少针对此语句发出错误

Buffer<int> b4 = getBuffer<int>("buf64"); 

如果不允许省略,则将调用移动构造函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多