Допустим, у меня есть функция, которая асинхронно отправляет 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>
, а затем переместить этот указатель в лямбду, но этот код проще.
Итак, безопасно ли это делать?
@IgorTandetnik хороший момент для порядка оценки, измененный пример, чтобы избежать этого
Проблема оптимизации небольшой строки все еще остается. str.c_str()
может быть указателем на буфер внутри объекта str
и, конечно, станет висячим, когда str
будет уничтожен.
@IgorTandetnik конечно, просто хочу избежать ответов, которые не имеют отношения к теме, поэтому устранил эту двусмысленность.
Кроме того, сама лямбда будет скопирована в член std::function
, string
и все такое. Строка в любом случае будет скопирована, оставив исходный указатель висеть. Лямбда, которая в конечном итоге будет запущена, — это не тот же объект, созданный в send
.
@ИгорТандетник, вы правы std::function
требует копирования лямбды, поэтому я даже не могу использовать std::unique_ptr
только std::shared_ptr
в этом случае. Проклятие.
Похоже, мой вопрос разделяется на 2: о c_str()
после перемещения и перемещении лямбды в std::function
. (и второй уже дан ответ на SO)
Вместо лямбды вы можете определить явный именованный класс, который будет действовать как обратный вызов. Затем этот класс может реализовать желаемую семантику управления жизненным циклом. Вы также можете заставить его раскрыть char*
, которым он сам управляет (через участника std::string
или иным образом), вместо того, чтобы полагаться на то, что str.c_str()
выживет достаточно долго.
Что бы сделал Раст?
@IgorTandetnik, к сожалению, sendData
предоставляется библиотекой, поэтому изменять ее было бы нецелесообразно. Я могу использовать std::shared
ptr или сохранить строку в контейнере и потом стереть ее. Это решение выглядело очень простым. Ну что ж.
Вам не нужно модифицировать sendData
. std::function
не ограничивается оберткой лямбда-выражений, он может обертывать любой вызываемый объект. Вы можете написать свой собственный.
Важно понимать, что объекты «перемещено» и «перемещено в» — это два разных объекта. Это не один объект, который в результате какого-то загадочного процесса исчезает в одном месте и снова появляется где-то в другом. Это не то, что такое ход.
Перемещенный объект все еще существует, и когда он выходит за пределы области видимости и уничтожается, его деструктор вызывается обычным образом.
Сам стандарт C++ на самом деле не определяет, что именно означает перемещение. В своих собственных классах вы можете определить конструктор перемещения и оператор присваивания перемещения, чтобы делать что угодно. Включая ничего. Все, что делает для вас C++, сам язык, — это формальное определение условий, требующих вызова конструктора перемещения или оператора присваивания перемещения. Вот и все. Это работа компилятора C++: вызывать конструктор или оператор присваивания, когда они имеют на это право. Скудное объяснение того, что на самом деле представляет собой перемещение после его вызова, определяется в стандарте C++ как утверждение, что: для классов, определенных в библиотеке C++, перемещение оставляет перемещенный объект в некотором «действительном, но неуказанном состоянии».
Указатель, возвращаемый из c_str()
, принадлежит его std::string
. Он действителен только до тех пор, пока его объект-владелец не будет каким-либо образом изменен. Превращение этого объекта в некое «действительное, но неопределенное состояние» помогло бы. Указатель из c_str()
больше не действителен. Тот факт, что std::string
был перенесен куда-то еще, не имеет значения и не меняет этого. В спецификации семантики c_str()
стандарта C++ нет ничего, что дополнительно уточняло бы его требования в отношении перемещаемых объектов.
Нет, это не безопасно. Указатель, возвращаемый
c_str()
, вполне может стать недействительным в результате перемещения (например, в случае оптимизации небольшой строки). Кроме того, возможно, что второй аргумент (лямбда) создается первым, и вы вызываетеc_str()
для объекта, который уже был перемещен.