Как std :: move делает недействительным значение исходной переменной?

В следующих примерах из ссылка cpp:

#include <iostream>
#include <utility>
#include <vector>
#include <string>

int main()
{
std::string str = "Hello";
std::vector<std::string> v;

// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";

// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the
// Contents of str will be moved into the vector.  This is
// less expensive, but also means str might now be empty.
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n";

std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n";
}

С помощью std::move может привести к потере исходного значения. Для меня это выглядит как

v.push_back(std::move(str))

вызывает новый член v[1] создается Затем,

&v[1] = &str

Но почему это должно повредить значение в str? Это не имеет смысла.

Есть много сложных уроков о std::move которые сложнее, чем мой собственный вопрос, чтобы понять.

Может ли кто-нибудь, пожалуйста, напишите

v.push_back(std::move(str))

в его эквиваленте с использованием c++03?

Я ищу объяснение, чье понимание легко и не содержит таких предпосылок, как x-value , static_cast, remove_referenceи т. д., поскольку они сами требуют понимания std::move первый. Пожалуйста, избегайте этой круговой зависимости.

Также эти ссылки не отвечают на мой вопрос: 7510182, 3413470

Потому что мне интересно знать, как str вред, а не то, что происходит с v[1],

Также приветствуется псевдокод, поскольку он так прост, как c++03,


Обновление: чтобы избежать осложнений, давайте рассмотрим более простой пример int следующее

int x = 10;
int y = std::move(x);
std::cout << x;

-2

Решение

В зависимости от реализации, std::move может быть простой обмен адресов внутренней памяти.

Если вы запустите следующий код на http://cpp.sh/9f6ru

#include <iostream>
#include <string>

int main()
{
std::string str1 = "test";
std::string str2 = "test2";

std::cout << "str1.data() before move: "<< static_cast<const void*>(str1.data()) << std::endl;
std::cout << "str2.data() before move: "<< static_cast<const void*>(str2.data()) << std::endl;

str2 = std::move(str1);
std::cout << "=================================" << std::endl;

std::cout << "str1.data() after move: " << static_cast<const void*>(str1.data()) << std::endl;
std::cout << "str2.data() after move: " << static_cast<const void*>(str2.data()) << std::endl;
}

Вы получите следующий вывод:

str1.data() before move: 0x363d0d8
str2.data() before move: 0x363d108
=================================
str1.data() after move: 0x363d108
str2.data() after move: 0x363d0d8

Но результат может отличаться в зависимости от реализации компилятора и библиотеки std.

Но детали реализации могут быть еще более сложными http://cpp.sh/6dx7j. Если вы посмотрите на свой пример, то увидите, что создание копии для строки не обязательно требует выделения новой памяти для ее содержимого. Это потому, что почти все операции на std::string только для чтения или требуют выделения памяти. Так что реализация может решить сделать только мелкие копии:

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

int main()
{
std::string str = "Hello";
std::vector<std::string> v;

std::cout << "str.data() before move: "<< static_cast<const void*>(str.data()) << std::endl;

v.push_back(str);
std::cout << "============================" << std::endl;
std::cout << "str.data()  after push_back: "<< static_cast<const void*>(str.data()) << std::endl;
std::cout << "v[0].data() after push_back: "<< static_cast<const void*>(v[0].data()) << std::endl;

v.push_back(std::move(str));
std::cout << "============================" << std::endl;

std::cout << "str.data()  after move: "<< static_cast<const void*>(str.data()) << std::endl;
std::cout << "v[0].data() after move: "<< static_cast<const void*>(v[0].data()) << std::endl;
std::cout << "v[1].data() after move: "<< static_cast<const void*>(v[1].data()) << std::endl;
std::cout << "After move, str is \"" << str << "\"\n";str = std::move(v[1]);
std::cout << "============================" << std::endl;
std::cout << "str.data()  after move: "<< static_cast<const void*>(str.data()) << std::endl;
std::cout << "v[0].data() after move: "<< static_cast<const void*>(v[0].data()) << std::endl;
std::cout << "v[1].data() after move: "<< static_cast<const void*>(v[1].data()) << std::endl;
std::cout << "After move, str is \"" << str << "\"\n";
}

Выход

str.data() before move: 0x3ec3048
============================
str.data()  after push_back: 0x3ec3048
v[0].data() after push_back: 0x3ec3048
============================
str.data()  after move: 0x601df8
v[0].data() after move: 0x3ec3048
v[1].data() after move: 0x3ec3048
After move, str is ""============================
str.data()  after move: 0x3ec3048
v[0].data() after move: 0x3ec3048
v[1].data() after move: 0x601df8
After move, str is "Hello"

И если вы посмотрите на:

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

int main()
{
std::string str = "Hello";
std::vector<std::string> v;

std::cout << "str.data() before move: "<< static_cast<const void*>(str.data()) << std::endl;

v.push_back(str);
std::cout << "============================" << std::endl;
str[0] = 't';
std::cout << "str.data()  after push_back: "<< static_cast<const void*>(str.data()) << std::endl;
std::cout << "v[0].data() after push_back: "<< static_cast<const void*>(v[0].data()) << std::endl;

}

Тогда вы предполагаете, что str[0] = 't' просто заменит данные на месте. Но это не обязательно так http://cpp.sh/47nsy.

str.data() before move: 0x40b8258
============================
str.data()  after push_back: 0x40b82a8
v[0].data() after push_back: 0x40b8258

И движущиеся примитивы, такие как:

void test(int i) {
int x=i;
int y=std::move(x);
std::cout<<x;
std::cout<<y;
}

В основном будет полностью оптимизирован компилятором:

  mov ebx, edi
mov edi, offset std::cout
mov esi, ebx
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov edi, offset std::cout
mov esi, ebx
pop rbx
jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int) # TAILCALL

И то и другое std::cout использовал тот же регистр, x а также y полностью оптимизированы.

2

Другие решения

std::move простая ссылка на приведение к значению. Это на самом деле не делать что-нибудь.

Вся магия происходит в функциях получение такая ссылка rvalue, если они принимают ее как ссылку rvalue. Они принимают это как лицензия на разграбление эти объекты безжалостно, избавляя от необходимости фактически распределять ресурсы и в противном случае выполнять тяжелую работу по созданию копии. При назначении ожидается замена источника и цели, чтобы избежать даже необходимости очистки.

Таким образом, использование семантики перемещения, как правило, более эффективно (можно делать меньше быстрее, поверьте мне) и с меньшей вероятностью вызывает исключения (получение ресурсов может привести к сбою) за счет уничтожения источника.

И все это благодаря тому маленькому замаскированному составу, известному как std::moveчто само по себе не делать что-нибудь.

3

По вопросам рекламы [email protected]