Я немного смущен, почему std::move(std::string)
не переводит переданный std::string
аргумент в пустое состояние (я имею в виду std::string
размер как 0 и его внутренний буфер, указывающий на nullptr
после вызова std::move(std::string)
). Это пример кода
#include <iostream>
#include <string>
void print(std::string& str) {
std::cout << "lref\n";
std::cout << str << "\n" << std::endl;
}
void print(const std::string& str) {
std::cout << "const lref\n";
std::cout << str << "\n" << std::endl;
}
void print(std::string&& str) {
std::cout << "rref\n";
std::cout << str << "\n" << std::endl;
}
int main() {
std::string str_a = "Hello, ";
std::string str_b = "world!";
print(str_a);
print(str_b);
print(str_a + str_b);
print(std::move(str_a));
print(str_a); // was expecting str_a to be empty but still prints Hello,
return 0;
}
Вы ожидали неправильно. std::move
создайте только ссылку rvalue, она ничего не «перемещает» - это нужно сделать с помощью конструктора перемещения или назначения перемещения.
Это не обязательно. Все, что ему нужно сделать, это оставить перемещенный объект в состоянии, которое не указано, но допустимо для переназначения. Не используйте перемещенный объект до тех пор, пока вы не переназначите его или иным образом не сбросите, потому что вы не можете зависеть от его состояния.
Отвечает ли это на ваш вопрос? Почему перемещение std::Optional не сбрасывает состояние
Например, ваша реализация может выполнять оптимизацию коротких строк (SSO) и, таким образом, даже не указывать на буфер, а просто хранить char
внутри. В этом случае нечего освобождать и нет опасности двойного освобождения одного и того же (несуществующего) буфера, поэтому ничего очищать не нужно. Но опять же, делает это или нет, на практике не имеет значения, потому что вы просто не должны использовать объект после перемещения, если вы сначала не переназначили/сбросили его в известное состояние, чего не делают такие операции перемещения. т дать.
@underscore_d, если я переназначу str_a
, будет ли другой объект (к которому я перешел) отражать новое переназначенное значение?
@Гарри Конечно нет. Как и любой другой объект C/C++, если не предусмотрено иное, std::string
является типом значения, а не ссылочным типом. Таким образом, присвоение str_a
меняет только то, что видят люди, которые смотрят на str_a
, или ссылку на него, но не что-либо еще.
@underscore_d допустим, у меня есть глобальная переменная g_str
и str_a = "Hello, "
внутри main
и функцияfoo(std::string&& a) { g_str = std::move(a); }
. Теперь в main
, когда я звоню foo(std::move(str_a); str_a = "abc";
. g_str
печатает Hello,
или abc
после переназначения str_a
?
@Harry Первое, как я уже объяснил, и как вам легко проверить, и рассуждать, зная, что std::string
не является ссылочным типом.
Даже если строка действительно была перемещена из (поскольку ее не было в этом коде), строка остается в допустимом, но неуказанном состоянии. Я считаю, что это, как правило, хорошее ожидание: если вы std::move
объект, объект подходит для повторного назначения или уничтожения, и это все... с некоторыми дополнительными гарантиями для некоторых типов объектов (например, std: :vector и std::unique_ptr и std::shared_ptr имеют более сильные гарантии). Говарду Хиннанту не нравился мой вариант «невмешательства», он говорил (перефразируя), что перемещаемый объект находится в любом состоянии, которое гарантирует программист.
@underscore_d Причина, по которой я спросил, заключается в том, что я думаю, что g_str
внутреннее char* buffer
теперь будет указывать на str_a
внутреннее char* buffer
после foo(std::move(a)
вызова, что предотвратит выделение новой памяти и копирование данных.
@Harry Если бы это случилось с оптимизацией, которую кто-то сделал, они также знали бы, что нужно прекратить ссылаться на один и тот же буфер из строки, из которой было перемещено, именно для того, чтобы избежать двух ссылок на него и возможности вмешательства или двойного освобождения. Доверяйте разработчикам stdlib.
@underscore_d, так что и str_b = std::move(str_a)
, и str_b = str_a
будут означать одно и то же, за исключением недопустимого состояния в случае std::move
, в основном нет никакой выгоды в производительности, которую я получу от str_b = std::move(str_a)
, str_b
будет иметь свою собственную новую выделенную память и копирование данных из str_a
до str_b
все еще происходит.
@ Гарри Нет, это неправда. Перемещение произойдет, если память была выделена динамически. Строка, к которой перемещается, получит право собственности на буфер строки, из которой была перемещена. Строка, из которой было перемещено, будет установлена таким образом, что она больше не будет ссылаться на этот буфер и не будет дважды освобождать его.
@underscore_d, предполагая, что память была выделена динамически, вы имеете в виду, что оператор str_b = std::move(str_a)
заставит str_b
указывать на str_a
внутреннюю char* buffer
, и когда вы переназначите str_a = "abc"
, str_a
внутренняя char* buffer
теперь будет указывать на другую область памяти, содержащую abc
?
@Harry Нет, как уже было сказано много раз, более вероятно, что в строке с перемещением будет nullptr
отсутствие буфера. Если вы видите abc
, возможно, он использует систему единого входа. В любом случае он не будет указывать на тот же динамически выделенный буфер, что и другой экземпляр string
. Опять же, верьте, что разработчики stdlib напишут для вас эффективный код и переместят динамически выделенный буфер, если он существует, и не будут затем дважды ссылаться на него или освобождать его. Но я не вижу больше способов объяснить, как они могут это сделать.
std::move
ничего не двигается. Он привел свой аргумент к rvalue-reference
. Чтобы на самом деле переместить, вам нужно передать str
как rvalue-reference
, чтобы переместить конструктор (или переместить назначение) другой строки, как показано ниже.
std::string new_str(std::move(str));
print(str);
и даже в этом случае конструктор перемещения или оператор присваивания перемещения класса могут решить не очищать значение, потому что по соглашению перемещение не обязательно (но они должны документировать то, что они делают в любом случае)
Должны ли они действительно документировать это? Операции перемещения должны переводить перемещенный объект только в безопасное состояние для уничтожения. После этого пользователи не должны принимать какое-либо конкретное состояние и должны просто прекратить использование перемещенного объекта.
@NicolasDusart Говорит ли Стандарт, что любой, кто реализует конструктор перемещения или оператор присваивания перемещения, должен это делать? Если да, то где и требуется ли это всем или только собственной стандартной библиотеке? В любом случае, даже если Стандарт устанавливает позицию по умолчанию для этого, я думаю, что могу утверждать, что документирование этого в любом случае хорошо. Можно просто сказать: «Я делаю то, что говорит соглашение stdlib», или «Я гарантирую эти дополнительные вещи», или «Я делаю что-то совершенно другое». С такой хорошей документацией таких вопросов не возникнет.
@underscore_d, если они хотят выполнить операцию «перемещения», которая заканчивается в определенном состоянии, предпочтительно предлагать ее через определенную функцию. Гораздо лучше придерживаться средних ожиданий C++ относительно конструкторов перемещения и присваивания без документации, чем конкретные конструкторы перемещения и присваивания с очень хорошей документацией. Люди не будут склонны читать документацию, если увидят обычное задание перемещения, оно должно просто делать то, что они от него ожидают.
@NicolasDusart Где говорится, что ожидание задокументировано, была моя точка зрения, которую я нашел здесь, а другая моя точка зрения заключалась в том, что кому-то другому, документирующему свои собственные операции перемещения, все равно не повредит сказать: «Мы делаем то же самое, что и stdlib делает здесь». Я согласен, что они должны делать то же самое, но я не думаю, что явное документирование этого вредит.
@underscore_d я хочу сказать, что они, по-видимому, должны документировать то, что они делают. Как правило, это детали реализации, как вы крадете ресурсы и все еще действительны для переназначения или уничтожения. Вероятно, это связано с текущей реализацией, если вы можете обеспечить надежную безопасность перемещения или нет, и как только вы это задокументируете, вы немного застряли. Я не говорю, что документировать это плохо, но я бы не сказал, что мы всегда должны документировать то, что делает ваша операция перемещения, помимо перевода объекта в допустимое состояние для уничтожения и присвоения, но не для других функций.
Этого делать не следует. Перемещенный объект должен находиться в допустимом, но неопределенном состоянии.