std :: string move构造函数实际是否移动?

时间:2019-01-29 11:53:32

标签: c++ c++11 move move-semantics stdstring

所以这里有一个小型测试程序:

#include <string>
#include <iostream>
#include <memory>
#include <vector>

class Test
{
public:
  Test(const std::vector<int>& a_, const std::string& b_)
    : a(std::move(a_)),
      b(std::move(b_)),
      vBufAddr(reinterpret_cast<long long>(a.data())),
      sBufAddr(reinterpret_cast<long long>(b.data()))
  {}

  Test(Test&& mv)
    : a(std::move(mv.a)),
      b(std::move(mv.b)),
      vBufAddr(reinterpret_cast<long long>(a.data())),
      sBufAddr(reinterpret_cast<long long>(b.data()))
  {}

  bool operator==(const Test& cmp)
  {
    if (vBufAddr != cmp.vBufAddr) {
      std::cout << "Vector buffers differ: " << std::endl
        << "Ours: " << std::hex << vBufAddr << std::endl
        << "Theirs: " << cmp.vBufAddr << std::endl;
      return false;
    }

    if (sBufAddr != cmp.sBufAddr) {
      std::cout << "String buffers differ: " << std::endl
        << "Ours: " << std::hex << sBufAddr << std::endl
        << "Theirs: " << cmp.sBufAddr << std::endl;
      return false;
    }
  }

private:

  std::vector<int> a;
  std::string b;
  long long vBufAddr;
  long long sBufAddr;
};

int main()
{
  Test obj1 { {0x01, 0x02, 0x03, 0x04}, {0x01, 0x02, 0x03, 0x04}};
  Test obj2(std::move(obj1));

  obj1 == obj2;


  return 0;
}

我用于测试的软件:

  

编译器:gcc 7.3.0

     

编译器标志:-std = c ++ 11

     

OS:Linux Mint 19(塔拉)和上游发行版Ubuntu 18.04 LTS(仿生)

我在这里看到的结果是,移动后,向量缓冲区仍然具有相同的地址,但字符串缓冲区没有。因此,在我看来,它分配了新的,而不只是交换缓冲区指针。是什么原因导致这种行为?

1 个答案:

答案 0 :(得分:36)

您可能会看到small/short string optimization的影响。为了避免对每个微小的小字符串进行不必要的分配,许多std::string的实现都包括一个固定大小的小型数组,以容纳小的字符串而无需使用new(该数组通常用于其他一些不需要的成员)在没有使用动态分配的情况下是必需的,因此无论是小型string还是大型std::move,它消耗很少或没有额外的内存来提供它,并且这些字符串不能从g++中受益(但是它们很小,所以很好)。较大的字符串将需要动态分配,并且将按您期望的那样传递指针。

仅用于演示,此代码位于void move_test(std::string&& s) { std::string s2 = std::move(s); std::cout << "; After move: " << std::hex << reinterpret_cast<uintptr_t>(s2.data()) << std::endl; } int main() { std::string sbase; for (size_t len=0; len < 32; ++len) { std::string s1 = sbase; std::cout << "Length " << len << " - Before move: " << std::hex << reinterpret_cast<uintptr_t>(s1.data()); move_test(std::move(s1)); sbase += 'a'; } }

NUL

Try it online!

产生高(堆栈)地址,该地址在长度为15或更短(随体系结构指针大小而变化)的移动构造上改变,但是一旦到达长度16或更高长度,切换到低(堆)地址,这些地址在移动构造后保持不变(由于C ++ 11及更高版本需要此开关,因此开关在16,而不是17,因为它是<html> <head> <meta charset="UTF-8" /> <style> .img { display: block; width: 412px; height: 183px; } </style> </head> <body style="margin: 0px"> <img class="img" alt="" src="https://dummyimage.com/412x183/2c38bd/000000"> <img class="img" alt="" src="https://dummyimage.com/412x183/2c38bd/000000"> </body> </html> 终止字符串)。

要100%清除:这是一个实现细节。 C ++规范的任何部分都没有要求这种行为,因此,您完全不应该依赖它发生,并且当它出现时,对于特定的字符串长度,您也不应该依赖它发生。