Конструктор перемещения std :: string действительно перемещается? - PullRequest
0 голосов
/ 29 января 2019

Итак, я получил небольшую тестовую программу:

#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

ОС: Linux Mint 19 (tara) с вышедшим выпуском Ubuntu 18.04 LTS (bionic)

Результаты, которые я вижу здесь, после переезда,векторный буфер все еще имеет тот же адрес, но строковый буфер не имеет.Так что мне кажется, что он выделил новый, а не просто поменял местами указатели буфера.Что вызывает такое поведение?

1 Ответ

0 голосов
/ 29 января 2019

Вероятно, вы видите эффект оптимизации маленькой / короткой строки .Чтобы избежать ненужных выделений для каждой крошечной строки, многие реализации 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';
    }
}

Попробуйте онлайн!

создает высокие (стековые) адреса, которые изменяются при построении перемещения на длины 15 или менее (предположительно, зависят от размера указателя архитектуры), но переключаются на низкие (куча) адреса, которые остаются неизменными после построения перемещения после того, как выдлина удара 16 или выше (переключатель на 16, а не на 17, потому что он NUL - определяет строки, поскольку C ++ 11 и выше требуют этого).

Чтобы быть на 100% ясным: этоэто деталь реализации.Ни одна часть спецификации C ++ не требует такого поведения, поэтому вам не следует полагаться на то, что оно вообще происходит, и когда оно происходит, вы не должны полагаться на то, что оно происходит для определенных длин строк.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...