Open sigasigasiga opened 1 year ago
чем плохи std::string_view и std::span ?
@sergii-rybin-tfs они не освободят ресурс при вызове деструктора
ясно, я бы завернул пару string_view+unique_ptr например в tuple<unique_ptr,string_view>.
Можно добавить читабельный алиас и оператор неявного преобразования типа и deduction guide.
в плюсовую библиотеку, которая принимает какой-то contiguous контейнер (например, std::vector или std::string)
ещё один вариант аллокатора, который использует malloc и free
но контейнер с другим аллокатором – это другой контейнер, который нельзя будет передать в плюсовую библиотеку, если она принимает обычные std::string
или std::vector
.
@pavelkryukov возможно, тут моя аргументация даёт течь. попробую привести другой пример, который нередко встречается в жизни: мы пишем плюсовую обёртку вокруг C'шной бибилотеки.
почти в каждой C'шной либе есть свои велосипеды с реализацией динамического массива. чтобы сделать из этого что-то более или менее приемлемое для использования в плюсовом мире, нужно будет, по сути, написать кучу бойлерплейта.
с предлагаемыми мною изменениями всё это оборачивается одним вызовом конструктора.
ко всему прочему, проблема необходимости привязки к конкретным аллокаторам в случаях, когда это совершенно не важно, была адресована появлением std::pmr::vector
придумал ещё один вариант использования предлагаемой мною фичи:
можно будет конвертировать std::vector
в std::string
совершенно бесплатно и без лишних мучений.
ещё один пример, где это было бы очень полезно.
представим, нам нужно передать владение указателем на массив в C'шную библиотеку, и перед вызовом нам надо этот массив как-то подготовить (т.е. заполнить какими-то данными).
с предлагаемыми изменениями мы просто можем завести std::vector<char, mallocator>
, заполнить его в привычной для C++ программиста манере, а затем release
'нуть подготовленный массив, и передать его туда куда надо
нам нужно передать владение указателем на массив в C'шную библиотеку, и перед вызовом нам надо этот массив как-то подготовить (т.е. заполнить какими-то данными).
Это решается через std::span
:
auto* ptr = (struct char*)malloc(sizeof(char) * size);
std::span<char> span(ptr, size);
fill_span(span.begin(), span.end()); // заполнение в привычной для C++ манере
pass_to_c_library(ptr);
придумал ещё один вариант использования предлагаемой мною фичи: можно будет конвертировать std::vector в std::string совершенно бесплатно и без лишних мучений.
Возможно, будут проблемы, если std::string
имплементирован через SSO.
Плюс, мне кажется, должно хватать конвертации владеющих типов в невладеющие: std::string_view
или std::span
.
Если кто-то принимает объект владеющего класса, то без копии скорее всего не обойтись.
P.S. что будет с нуль-терминированием строк, созданных из std::vector
?
Ещё вариант — использовать std::unique_ptr<T[]>
или std::shared_ptr<T[]>
с кастомным deleter-ом
Это решается через
std::span
:
не решается. у вектора есть возможность удобно расширять размер буфера, у спана такого нет
Возможно, будут проблемы, если
std::string
имплементирован через SSO.
в таком случае можно выделять буфер в куче, всё туда копировать и возвращать указатель на него. таким образом мы, к сожалению, теряем noexcept
, но это, наверное, не так страшно
P.S. что будет с нуль-терминированием строк, созданных из
std::vector
?
в нашем любимом языке проблемы подобного толка решаются довольно просто. в строку должны будут передаваться нуль-терминированные строки, иначе ub :)
Ещё вариант — использовать
std::unique_ptr<T[]>
илиstd::shared_ptr<T[]>
с кастомным deleter-ом
там тоже нет всей этой логики с увеличением размера буфера при необходимости. да и вообще там ничего нет, всё приходится делать руками. такое решение получается слишком низкоуровневым
Внутри std::stirng вообще может не быть указателя, изза short string optimization. https://stackoverflow.com/questions/21694302/what-are-the-mechanics-of-short-string-optimization-in-libc Поэтому хакнуть дизайн STL изначально разработанной под deep copy не выйдет. Если нужно copy on write - его надо делать самостоятельно. Для общего случая ввели универстальный класс std::string который скорее для имитации строк других языков, т..е. для функциональности на все случаи жизни.
@incoder1 на аргумент про SSO я уже ответил вот тут. а при чём тут copy-on-write я, честно говоря, не понял
@sigasigasiga передача владения из контейнера это вариант copy-on-write с shallow copy вместо обычного deep copy. Изначальный дизайн STL - дополнить язык выско-уровневыми типами данных, аналогичными другим языкам с помощью шаблонов и ООП. В частности строками (в С и голом С++ есть только массивы символов), динамическими и ассоциативными массивами. То что вы предлагаете - это другой дизайн.
@incoder1
передача владения из контейнера это вариант copy-on-write с shallow copy вместо обычного deep copy.
честно говоря, я всё ещё не понимаю, при чём тут copy-on-write. ведь cow -- это, по своей сути, просто отложенное копирование объекта, позволяющее делать копию не сразу, а в момент, когда она действиельно потребуется. то, что предлагаю я, с копированием напрямую никак не связано.
То что вы предлагаете - это другой дизайн.
тут, как мне кажется, можно удариться в жуткую и изматывающую демагогию. не сказал бы, что это другой дизайн. под другим дизайном я бы понимал глобальное переосмысление всего интерфейса векторов и строк, ровно как и переосмысление принципов их работы. я же никаких прям глобальных изменений не предлагаю, лишь обобщение идеи владения ресурсами на те стандартные контейнеры, где это смотрелось бы уместно.
В целом про демагогию согласен. Договорится за дизайн очень сложно, противоборствующие лагеря с конфликтом интересов найдут миллион аргументов с каждой стороны не приходить к консенсусу. Примерно по этой причине 3 года не могут согласовать пулы потоков. Тут дело в следующем дизайну STL Cтепанова и Ли уже 29 лет, на нем завязано много существующего кода, трюков - гавный из который - взятие адреса нулевого элемента из контейнера для получения указателя, скажем классический трюк для преобразования в UTF-16LE
#include <windows.h>
#include <iostream>
#include <string>
std::wstring transcode(const char* src)
{
int len = ::MultiByteToWideChar(CP_UTF8, 0, src, -1, nullptr, 0);
std::wstring ret(len, L'\0');
::MultiByteToWideChar(CP_UTF8, 0, src, -1, &ret[0], len);
return ret;
}
int main(int argc, const char** argv)
{
std::wcout << ::transcode("Hello world!") << std::endl;
return 0;
}
Под эти трюки даже были дополнения в стандарт, например стандарт требует чтобы блок памяти под строку в векторе и строке был сплошным. Ваше предложение - хакнуть дизайн и возможно сломать все эти воркераунды миллионах строк кода тысячь проектов созданных за эти почти 30 лет. Что будет если мы изъяли владение указателем на блок памяти из контейнера, а потом кусок кода начинает обращаться к адресу по нулевого элемента этого контейнера ? ИМХО - проще создать вообще праралельную библиотеку контейнеров которая по другому обращается с памятью, в частности ведут себя как скажем строки Java и C# - множество интеллектуальных указателей на один массив, copy-on-write и т.д. а существующие контейнеры оставить в покое.
@incoder1 вы говорите чересчур абстрактно. можете, пожалуйста, привести какую-нибудь конкретную ситуацию, где предлагаемые мною изменения что-то сломают в существующем коде?
взятие адреса нулевого элемента из контейнера для получения указателя
предлагаемые мною изменения это поведение не меняют
Ваше предложение - хакнуть дизайн и возможно сломать все эти воркераунды миллионах строк кода тысячь проектов созданных за эти почти 30 лет
почему же? если в древний код не добавлять использование предлагаемого мною метода release
, то он будет работать как прежде.
Что будет если мы изъяли владение указателем на блок памяти из контейнера, а потом кусок кода начинает обращаться к адресу по нулевого элемента этого контейнера ?
UB, точно так же, как если мы будем разыменовывать unique_ptr
, у которого забрали владение указателем.
C++ хорош тем, что он позволяет без лишней возни и оверхеда использовать C'шные библиотеки. однако в некоторых сценариях межъязыковое взаимодействие всё-таки даёт о себе знать: предположим, C'шная библиотека возвращает какой-то буфер, владение которым отдаётся на откуп пользователю, а затем этот буфер надо передать уже в плюсовую библиотеку, которая принимает какой-то contiguous контейнер (например,
std::vector
илиstd::string
). в этом случае не видится никакого другого решения, кроме как скопировать буфер из C'шной библиотеки в контейнер, а затем его удалить. копирование на пустом месте!примерно та же история может быть работать в обратную сторону: может потребоваться отдать в C'шную библиотеку владение указателем, который принадлежит
std::vector
'у илиstd::string
'у, и в этом случае тоже придётся делать абсолютно бессмысленное копирование.предлагается дать возможность контейнерам более гибко работать с владением буферами.
передача владения в контейнер наверное, стоит сделать новый конструктор от четырёх аргументов:
pointer
,size
,capacity
иallocator
-- такой вариант конструктора, насколько я вижу, ещё никем не использован. возможно, лучше бы было добавить статический метод в класс, чтобы его имя лучше отражало суть происходящего, однако судя по тому, что в комитете ни разу подобным образом не поступили, такой вариант вряд ли прийдётся кому-то по душе.передача владения из контейнера для этого, думаю, стоит добавить метод
release
по аналогии с тем, что сейчас есть вstd::unique_ptr
. возвращать он может какую-то структуру (но ни в коем случае неstd::tuple
, использование которого обречёт пользователей на написаниеstd::get</* SOME MAGIC NUMBER */>
), которая будет содержать указатель, размер, capacity и аллокатор.аллокаторы эти изменения вряд ли будет работать так просто -- в C'шном коде в лучшем случае память выделяется с помощью
malloc
'а и удаляется с помощьюfree
, аstd::allocator
делает это с помощьюnew
иdelete
соответственно, и между собой эти два варианта аллокации/деаллокации, к сожалению, несовместимы. поэтому, скорее всего, нужно будет добавить в стандартную библиотеку ещё один вариант аллокатора, который используетmalloc
иfree
(а назвать его можноmallocator
'ом:) ).