Std::move(std::string) не переводит переданный аргумент в пустое состояние

Я немного смущен, почему 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;
}

Этого делать не следует. Перемещенный объект должен находиться в допустимом, но неопределенном состоянии.

drescherjm 23.12.2020 16:16

Вы ожидали неправильно. std::move создайте только ссылку rvalue, она ничего не «перемещает» - это нужно сделать с помощью конструктора перемещения или назначения перемещения.

user1143634 23.12.2020 16:17

Это не обязательно. Все, что ему нужно сделать, это оставить перемещенный объект в состоянии, которое не указано, но допустимо для переназначения. Не используйте перемещенный объект до тех пор, пока вы не переназначите его или иным образом не сбросите, потому что вы не можете зависеть от его состояния.

underscore_d 23.12.2020 16:17

Отвечает ли это на ваш вопрос? Почему перемещение std::Optional не сбрасывает состояние

underscore_d 23.12.2020 16:18

Например, ваша реализация может выполнять оптимизацию коротких строк (SSO) и, таким образом, даже не указывать на буфер, а просто хранить char внутри. В этом случае нечего освобождать и нет опасности двойного освобождения одного и того же (несуществующего) буфера, поэтому ничего очищать не нужно. Но опять же, делает это или нет, на практике не имеет значения, потому что вы просто не должны использовать объект после перемещения, если вы сначала не переназначили/сбросили его в известное состояние, чего не делают такие операции перемещения. т дать.

underscore_d 23.12.2020 16:19

@underscore_d, если я переназначу str_a, будет ли другой объект (к которому я перешел) отражать новое переназначенное значение?

Harry 23.12.2020 16:32

@Гарри Конечно нет. Как и любой другой объект C/C++, если не предусмотрено иное, std::string является типом значения, а не ссылочным типом. Таким образом, присвоение str_a меняет только то, что видят люди, которые смотрят на str_a, или ссылку на него, но не что-либо еще.

underscore_d 23.12.2020 16:35

@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 23.12.2020 16:42

@Harry Первое, как я уже объяснил, и как вам легко проверить, и рассуждать, зная, что std::string не является ссылочным типом.

underscore_d 23.12.2020 16:43

Даже если строка действительно была перемещена из (поскольку ее не было в этом коде), строка остается в допустимом, но неуказанном состоянии. Я считаю, что это, как правило, хорошее ожидание: если вы std::move объект, объект подходит для повторного назначения или уничтожения, и это все... с некоторыми дополнительными гарантиями для некоторых типов объектов (например, std: :vector и std::unique_ptr и std::shared_ptr имеют более сильные гарантии). Говарду Хиннанту не нравился мой вариант «невмешательства», он говорил (перефразируя), что перемещаемый объект находится в любом состоянии, которое гарантирует программист.

Eljay 23.12.2020 16:45

@underscore_d Причина, по которой я спросил, заключается в том, что я думаю, что g_str внутреннее char* buffer теперь будет указывать на str_a внутреннее char* buffer после foo(std::move(a) вызова, что предотвратит выделение новой памяти и копирование данных.

Harry 23.12.2020 16:51

@Harry Если бы это случилось с оптимизацией, которую кто-то сделал, они также знали бы, что нужно прекратить ссылаться на один и тот же буфер из строки, из которой было перемещено, именно для того, чтобы избежать двух ссылок на него и возможности вмешательства или двойного освобождения. Доверяйте разработчикам stdlib.

underscore_d 23.12.2020 16:57

@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 все еще происходит.

Harry 23.12.2020 17:44

@ Гарри Нет, это неправда. Перемещение произойдет, если память была выделена динамически. Строка, к которой перемещается, получит право собственности на буфер строки, из которой была перемещена. Строка, из которой было перемещено, будет установлена ​​таким образом, что она больше не будет ссылаться на этот буфер и не будет дважды освобождать его.

underscore_d 23.12.2020 17:50

@underscore_d, предполагая, что память была выделена динамически, вы имеете в виду, что оператор str_b = std::move(str_a) заставит str_b указывать на str_a внутреннюю char* buffer, и когда вы переназначите str_a = "abc", str_a внутренняя char* buffer теперь будет указывать на другую область памяти, содержащую abc?

Harry 23.12.2020 17:56

@Harry Нет, как уже было сказано много раз, более вероятно, что в строке с перемещением будет nullptr отсутствие буфера. Если вы видите abc, возможно, он использует систему единого входа. В любом случае он не будет указывать на тот же динамически выделенный буфер, что и другой экземпляр string. Опять же, верьте, что разработчики stdlib напишут для вас эффективный код и переместят динамически выделенный буфер, если он существует, и не будут затем дважды ссылаться на него или освобождать его. Но я не вижу больше способов объяснить, как они могут это сделать.

underscore_d 23.12.2020 18:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
16
292
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

std::move ничего не двигается. Он привел свой аргумент к rvalue-reference. Чтобы на самом деле переместить, вам нужно передать str как rvalue-reference, чтобы переместить конструктор (или переместить назначение) другой строки, как показано ниже.

std::string new_str(std::move(str));
print(str);

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

underscore_d 23.12.2020 16:37

Должны ли они действительно документировать это? Операции перемещения должны переводить перемещенный объект только в безопасное состояние для уничтожения. После этого пользователи не должны принимать какое-либо конкретное состояние и должны просто прекратить использование перемещенного объекта.

Nicolas Dusart 23.12.2020 16:44

@NicolasDusart Говорит ли Стандарт, что любой, кто реализует конструктор перемещения или оператор присваивания перемещения, должен это делать? Если да, то где и требуется ли это всем или только собственной стандартной библиотеке? В любом случае, даже если Стандарт устанавливает позицию по умолчанию для этого, я думаю, что могу утверждать, что документирование этого в любом случае хорошо. Можно просто сказать: «Я делаю то, что говорит соглашение stdlib», или «Я гарантирую эти дополнительные вещи», или «Я делаю что-то совершенно другое». С такой хорошей документацией таких вопросов не возникнет.

underscore_d 23.12.2020 16:50

@underscore_d, если они хотят выполнить операцию «перемещения», которая заканчивается в определенном состоянии, предпочтительно предлагать ее через определенную функцию. Гораздо лучше придерживаться средних ожиданий C++ относительно конструкторов перемещения и присваивания без документации, чем конкретные конструкторы перемещения и присваивания с очень хорошей документацией. Люди не будут склонны читать документацию, если увидят обычное задание перемещения, оно должно просто делать то, что они от него ожидают.

Nicolas Dusart 23.12.2020 16:57

@NicolasDusart Где говорится, что ожидание задокументировано, была моя точка зрения, которую я нашел здесь, а другая моя точка зрения заключалась в том, что кому-то другому, документирующему свои собственные операции перемещения, все равно не повредит сказать: «Мы делаем то же самое, что и stdlib делает здесь». Я согласен, что они должны делать то же самое, но я не думаю, что явное документирование этого вредит.

underscore_d 23.12.2020 16:59

@underscore_d я хочу сказать, что они, по-видимому, должны документировать то, что они делают. Как правило, это детали реализации, как вы крадете ресурсы и все еще действительны для переназначения или уничтожения. Вероятно, это связано с текущей реализацией, если вы можете обеспечить надежную безопасность перемещения или нет, и как только вы это задокументируете, вы немного застряли. Я не говорю, что документировать это плохо, но я бы не сказал, что мы всегда должны документировать то, что делает ваша операция перемещения, помимо перевода объекта в допустимое состояние для уничтожения и присвоения, но не для других функций.

Nicolas Dusart 23.12.2020 17:11

Другие вопросы по теме