На самом деле это проблема стандартизаторов, которые довели ситуацию до маразма. Выше уже дали ссылку на пропозал, который этот маразм пытается полечить.
Но вообще, конечно, лучше продумать архитектурку так
Ну дык понятно, что лучше быть здоровым и богатым, чем... ;)
Это может быть тупо невозможно, т.к. каждая функция может работать в своем треде и вообще ничего не знать про другую.
Ну, если он не в курсе, то он и всякие неоднозначные оптимизации применять не будет.
Так даже в таком тривиальном случае:
// Где-то в другом TU.
void create(std::span<std::byte> mem) { new(mem.data()) Demo{...}; }
// В моем TU.
void use(std::span<std::byte> mem) {
Demo * d = std::launder(reinterpret_cast<Demo*>(mem.data());
...
}
компилятору нечего оптимизировать, т.к. в моем TU внутри use никаких других объектов Demo или указателей на них нет и не было. Так что здесь, имхо, start_lifetime_as была бы даже логичнее.
Если по указателю находится объект, чей лайфтайм с точки зрения компилятора уже начался, то нужно использовать std::launder
Допустим, у нас есть буфер, в который мы загрузили значение из файла/пайпа. После чего передали указатель на этот буфер в две разные функции. Первая в C++23 должна использовать start_lifetime_as. Тем самым она начинает время жизни объекта.
Что должна делать вторая? Вызывать launder, т.к. первая уже лайфтайм для объекта начала? Или можно еще раз вызвать start_lifetime_as.
Плюс тому, launder/start_lifetime_as ограничены скоупом, в котором мы хотим использовать указатель на что-то. Компилятор понятия не имеет что происходит в других TU, в которых значение по указателю формируется. ХЗ был ли там уже начат лайфтайм или нет.
Если для более раннего стандарта, то там UB.
Так для того же C++17 другого способа и нет. И если компилятор в рамках 17-го стандарта начнет такой UB эксплуатировать, то это будет еще одним свидетельством, что эволюция языка и компиляторостроения пошла куда-то не туда.
std::start_lifetime_as нужен для создания объекта с состоянием уже хранящимся в байтах, где он будет размещен.
Насколько я знаю, start_lifetime_as не создает объект, а говорит компилятору о том, что появился новый объект о котором компилятор ранее не знал (т.е. он не был создан через new, как локальная переменная на стеке или каким-то другим известным компилятором способом). Т.е. программист говорит компилятору: вот в этой области памяти сейчас есть объект, пожалуйста, поверь, что это так.
В этой связи мне не понятно, чем область памяти, заполненная, например, чтением байт из файла или из пайпа, принципиально отличается от области памяти, в которой объект был ранее сконструирован через placement new.
У вас как раз launder нужен после того, как вы создали объекты через placement new, но типизированные указатели на них нигде не сохранили.
Но вот другой сценарий: https://wandbox.org/permlink/wTISrxXoKMlw0PUv (к сожалению, не удалось протестировать с start_lifetime_as, похоже, эту фичу пока еще в компилятор не завезли).
Здесь make_and_use_object для конструирования объекта вызывает разные функторы. И вот после возврата из функтора что должно применяться -- launder или start_lifetime_as? Ведь каждый из функторов заполняет переданный буфер по-разному?
И для launder здесь, вроде как, места нет, т.к. не было ранее объектов Data, на которые у меня могли бы быть указатели, и эти объекты бы пересоздавались.
В общем, я к тому, что после введения в язык start_lifetime_as стало не очень понятно в каком случае требуется launder, а в каком start_lifetime_as.
Да, есть такое дело. Но ведь в C++23 завозят std::start_lifetime_as и, поскольку в статье речь идет о C++26, может быть std::start_lifetime_as уместнее, чем std::launder?
PS. На самом деле я сам не знаю, что правильно. До C++23 вроде как std::launder необходим хотя бы для очистки совести. На вот начиная с C++23...
А точно ли здесь нужно использование std::launder? Вроде бы объект типа T инстанциируется корректно, затем его тип не меняется, а кастинг из std::byte* в T* полностью легален.
Во-первых, сложно и геморройно. Охота за утечками памяти в начале-середине 1990-х годов была одним из основных занятий при разработке на C++.
Во-вторых, умные указатели появились отнюдь не вместе с C++11. Я сам их начал использовать на повседневной основе во второй половине 1990-х (при этом не был пионером, а подсмотрел в чужих разработках, так что умные указатели в мире C++ уже лет тридцать как, если не больше). Ну а самый простой из них под видом std::auto_ptr был прямо в С++98.
ИМХО, этот момент имеет смысл отразить в статье. Потому что выравнивание -- это штука, про которые не все помнят, а кто-то и не знает. Скопируют ваш подход и наступят на грабли где-нибудь.
Могу ли я предположить, что игровой движок -- это небольшая часть конкретного игрового проекта и тот прессинг багов и нереализованных хотелок, про который упоминает автор статьи, относится не к игровому движку?
https://en.cppreference.com/w/cpp/links/libs -- этот список никакой не "официальный" Не знаю как сейчас, а несколько лет назад на cppreference можно было зарегистрироваться и внести изменения в wiki-страничку со списком библиотек. Так что там список того, что успели и не поленились добавить на эту wiki-страничку. Попасть в список awesome-cpp чуть сложнее, там (емнип) PR отправлялся и этот PR ждал пока его примут.
Потому что ее нет, вот и кажется, что это манна небесная. А как появится, как распробуем, так и начнем мечтать еще о чем-то недоступном в ожидании чуда :)
На самом деле это проблема стандартизаторов, которые довели ситуацию до маразма. Выше уже дали ссылку на пропозал, который этот маразм пытается полечить.
Ну дык понятно, что лучше быть здоровым и богатым, чем... ;)
Это может быть тупо невозможно, т.к. каждая функция может работать в своем треде и вообще ничего не знать про другую.
Так даже в таком тривиальном случае:
компилятору нечего оптимизировать, т.к. в моем TU внутри use никаких других объектов Demo или указателей на них нет и не было. Так что здесь, имхо,
start_lifetime_as
была бы даже логичнее.Жаль, что она как-то явно не описана :(
Допустим, у нас есть буфер, в который мы загрузили значение из файла/пайпа. После чего передали указатель на этот буфер в две разные функции. Первая в C++23 должна использовать start_lifetime_as. Тем самым она начинает время жизни объекта.
Что должна делать вторая? Вызывать launder, т.к. первая уже лайфтайм для объекта начала? Или можно еще раз вызвать start_lifetime_as.
Плюс тому, launder/start_lifetime_as ограничены скоупом, в котором мы хотим использовать указатель на что-то. Компилятор понятия не имеет что происходит в других TU, в которых значение по указателю формируется. ХЗ был ли там уже начат лайфтайм или нет.
Так для того же C++17 другого способа и нет. И если компилятор в рамках 17-го стандарта начнет такой UB эксплуатировать, то это будет еще одним свидетельством, что эволюция языка и компиляторостроения пошла куда-то не туда.
Насколько я знаю,
start_lifetime_as
не создает объект, а говорит компилятору о том, что появился новый объект о котором компилятор ранее не знал (т.е. он не был создан через new, как локальная переменная на стеке или каким-то другим известным компилятором способом). Т.е. программист говорит компилятору: вот в этой области памяти сейчас есть объект, пожалуйста, поверь, что это так.В этой связи мне не понятно, чем область памяти, заполненная, например, чтением байт из файла или из пайпа, принципиально отличается от области памяти, в которой объект был ранее сконструирован через placement new.
Я понимаю роль launder вот в таком сценарии: https://wandbox.org/permlink/wh0QVJSGhu41P0LH
И, насколько помню, именно для этого launder и создавался.
Но вот другой сценарий: https://wandbox.org/permlink/wTISrxXoKMlw0PUv
(к сожалению, не удалось протестировать с
start_lifetime_as
, похоже, эту фичу пока еще в компилятор не завезли).Здесь
make_and_use_object
для конструирования объекта вызывает разные функторы. И вот после возврата из функтора что должно применяться --launder
илиstart_lifetime_as
? Ведь каждый из функторов заполняет переданный буфер по-разному?И для
launder
здесь, вроде как, места нет, т.к. не было ранее объектов Data, на которые у меня могли бы быть указатели, и эти объекты бы пересоздавались.В общем, я к тому, что после введения в язык
start_lifetime_as
стало не очень понятно в каком случае требуетсяlaunder
, а в какомstart_lifetime_as
.Да, есть такое дело.
Но ведь в C++23 завозят
std::start_lifetime_as
и, поскольку в статье речь идет о C++26, может бытьstd::start_lifetime_as
уместнее, чемstd::launder
?PS. На самом деле я сам не знаю, что правильно. До C++23 вроде как
std::launder
необходим хотя бы для очистки совести. На вот начиная с C++23...А точно ли здесь нужно использование
std::launder
?Вроде бы объект типа T инстанциируется корректно, затем его тип не меняется, а кастинг из
std::byte*
вT*
полностью легален.Мой ответ вам -- это был сарказм на 146%. И речь шла про то, что в программировании еще никому не удавалось не совершать ошибок.
Неужели и вы про это?
Вы сейчас серьезно?
Так очевидно же, что писать на C++ без утечек памяти, use after free и double free просто: достаточно не совершать ошибок. Делов-то.
Во-первых, сложно и геморройно. Охота за утечками памяти в начале-середине 1990-х годов была одним из основных занятий при разработке на C++.
Во-вторых, умные указатели появились отнюдь не вместе с C++11. Я сам их начал использовать на повседневной основе во второй половине 1990-х (при этом не был пионером, а подсмотрел в чужих разработках, так что умные указатели в мире C++ уже лет тридцать как, если не больше). Ну а самый простой из них под видом std::auto_ptr был прямо в С++98.
ИМХО, этот момент имеет смысл отразить в статье. Потому что выравнивание -- это штука, про которые не все помнят, а кто-то и не знает. Скопируют ваш подход и наступят на грабли где-нибудь.
Ясно-понятно, вопросов больше не имею.
Кем и как этот размер подбирается?
Это утверждение относится к игровым движкам или к программированию на C++ вообще?
Нет ли здесь:
проблем с выравниванием для типа
_type
?Скажем, sizeof(counterSize) у вас 4, а выравнивание для
_type
-- 8.Звучит так, что "долгоживущий код" -- это редкость.
А зачем их брать? Или вы их тоже ревьювите?
Могу ли я предположить, что игровой движок -- это небольшая часть конкретного игрового проекта и тот прессинг багов и нереализованных хотелок, про который упоминает автор статьи, относится не к игровому движку?
https://en.cppreference.com/w/cpp/links/libs -- этот список никакой не "официальный"
Не знаю как сейчас, а несколько лет назад на cppreference можно было зарегистрироваться и внести изменения в wiki-страничку со списком библиотек. Так что там список того, что успели и не поленились добавить на эту wiki-страничку. Попасть в список awesome-cpp чуть сложнее, там (емнип) PR отправлялся и этот PR ждал пока его примут.
А насколько в игрострое распространены случаи, когда однажды написанная кодовая база затем эксплуатируется и развивается на протяжении 10 и более лет?
Стандарт с C++20, но там есть свои особенности.
Потому что ее нет, вот и кажется, что это манна небесная. А как появится, как распробуем, так и начнем мечтать еще о чем-то недоступном в ожидании чуда :)