Являются ли данные, возвращаемые std::string::c_str(), действительными после перемещения строки?

Допустим, у меня есть функция, которая асинхронно отправляет const char * данные и вызывает обратный вызов при отправке данных или возникновении ошибки (я пропустил обработку ошибок, поскольку она не имеет отношения к вопросу):

void sendData( const char *data, std::function<void()> );

Таким образом, пока не будет вызван обратный вызов, я должен поддерживать данные, переданные в функцию, как действительные.

Одно из решений — передать результат std::string::c_str() и переместить эту строку в обратный вызов:

 void send( std::string str )
 {
     auto data = str.c_str();
     sendData( data, [st=std::move(str)]() {} );
 }

Итак, str следует переместить в лямбду, и при ее вызове она должна уничтожить строку в конце.

Конечно, я могу переместить исходную строку в std::unique_ptr<std::string>, а затем переместить этот указатель в лямбду, но этот код проще.

Итак, безопасно ли это делать?

Нет, это не безопасно. Указатель, возвращаемый c_str(), вполне может стать недействительным в результате перемещения (например, в случае оптимизации небольшой строки). Кроме того, возможно, что второй аргумент (лямбда) создается первым, и вы вызываете c_str() для объекта, который уже был перемещен.

Igor Tandetnik 08.03.2024 01:01

@IgorTandetnik хороший момент для порядка оценки, измененный пример, чтобы избежать этого

Slava 08.03.2024 01:04

Проблема оптимизации небольшой строки все еще остается. str.c_str() может быть указателем на буфер внутри объекта str и, конечно, станет висячим, когда str будет уничтожен.

Igor Tandetnik 08.03.2024 01:05

@IgorTandetnik конечно, просто хочу избежать ответов, которые не имеют отношения к теме, поэтому устранил эту двусмысленность.

Slava 08.03.2024 01:08

Кроме того, сама лямбда будет скопирована в член std::function, string и все такое. Строка в любом случае будет скопирована, оставив исходный указатель висеть. Лямбда, которая в конечном итоге будет запущена, — это не тот же объект, созданный в send.

Igor Tandetnik 08.03.2024 01:08

@ИгорТандетник, вы правы std::function требует копирования лямбды, поэтому я даже не могу использовать std::unique_ptr только std::shared_ptr в этом случае. Проклятие.

Slava 08.03.2024 01:18

Похоже, мой вопрос разделяется на 2: о c_str() после перемещения и перемещении лямбды в std::function. (и второй уже дан ответ на SO)

Slava 08.03.2024 01:20

Вместо лямбды вы можете определить явный именованный класс, который будет действовать как обратный вызов. Затем этот класс может реализовать желаемую семантику управления жизненным циклом. Вы также можете заставить его раскрыть char*, которым он сам управляет (через участника std::string или иным образом), вместо того, чтобы полагаться на то, что str.c_str() выживет достаточно долго.

Igor Tandetnik 08.03.2024 01:22

Что бы сделал Раст?

Eljay 08.03.2024 01:26

@IgorTandetnik, к сожалению, sendData предоставляется библиотекой, поэтому изменять ее было бы нецелесообразно. Я могу использовать std::shared ptr или сохранить строку в контейнере и потом стереть ее. Это решение выглядело очень простым. Ну что ж.

Slava 08.03.2024 01:27

Вам не нужно модифицировать sendData. std::function не ограничивается оберткой лямбда-выражений, он может обертывать любой вызываемый объект. Вы можете написать свой собственный.

Igor Tandetnik 08.03.2024 01:29
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
101
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

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

Сам стандарт C++ на самом деле не определяет, что именно означает перемещение. В своих собственных классах вы можете определить конструктор перемещения и оператор присваивания перемещения, чтобы делать что угодно. Включая ничего. Все, что делает для вас C++, сам язык, — это формальное определение условий, требующих вызова конструктора перемещения или оператора присваивания перемещения. Вот и все. Это работа компилятора C++: вызывать конструктор или оператор присваивания, когда они имеют на это право. Скудное объяснение того, что на самом деле представляет собой перемещение после его вызова, определяется в стандарте C++ как утверждение, что: для классов, определенных в библиотеке C++, перемещение оставляет перемещенный объект в некотором «действительном, но неуказанном состоянии».

Указатель, возвращаемый из c_str(), принадлежит его std::string. Он действителен только до тех пор, пока его объект-владелец не будет каким-либо образом изменен. Превращение этого объекта в некое «действительное, но неопределенное состояние» помогло бы. Указатель из c_str() больше не действителен. Тот факт, что std::string был перенесен куда-то еще, не имеет значения и не меняет этого. В спецификации семантики c_str() стандарта C++ нет ничего, что дополнительно уточняло бы его требования в отношении перемещаемых объектов.

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