std::string vs const std::string& vs std::string_viewЗахват аргументовC++:...
std::string vs const std::string& vs std::string_view
Захват аргументовC++: std::vector.push_back() и классыКонвертация std::string в std::wstringИспользование std::string в dllВключение шаблонов и std::enable_ifИспользование std::move в списках инициализацииКак передать аргумент в std::thread по ссылке?Специализация шаблонной функции для std::stringC++,оптимизация подобия функции strstr(const char*,const char*)Запись текста в std::string дважды. С++Отличие между const string &s и string &s
Что правильнее передавать как аргумент функции(например в конструктор), если там эта строка будет просто скопирована?
c++
добавить комментарий |
Что правильнее передавать как аргумент функции(например в конструктор), если там эта строка будет просто скопирована?
c++
добавить комментарий |
Что правильнее передавать как аргумент функции(например в конструктор), если там эта строка будет просто скопирована?
c++
Что правильнее передавать как аргумент функции(например в конструктор), если там эта строка будет просто скопирована?
c++
c++
задан 12 часов назад
tim barstim bars
607
607
добавить комментарий |
добавить комментарий |
3 ответа
3
текущие
по дате публикации
голоса
Использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий. И это относится не только к std::string
и std::string_view
Для начала определимся с реализацией наших сущностей. Пусть std::string
построен на трех указателях по 4 байта каждый (или указатель и два размера):
class string
{
//...
pointer * m_begin; //Начало строки
pointer * m_end; //Указатель за последний элемент
pointer * m_end_of_storage; //Указатель за конец выделенной памяти
};
std::string_view
при этом реализован с помощью двух указателей (или указатель и размер):
class string_view
{
//...
pointer m_data; //Указатель на начало данных
pointer m_end; //Указатель за конец данных
};
Упрощенно рассмотрим три версии простого кода:
void other(std::string); //<-- функция, в которую всегда передается копия.
void run(string_view v) //<-- Функция, которая вызывает функцию other
{
//т.е. эта функция делает ту самую копию объекта
other(v);
}
void general(string str)
{
//А из этой функции в функцию run передается строка
other(str);
}
Что будет сгенерировано компилятором и как рассматривать не будем, т.к. это сильно зависит от компилятора и параметров компиляции, но постараемся посмотреть что может быть и как.
В нашем случае other
передает копию string_view
, т.е. general
копирует два указателя (не говорим о возможности вызова конструктора копирования, подразумевая, что он самый тривиальный и в результирующем коде приведет просто к копированию двух членов класса). Затем строка копируется из string_view
. Ничего сложного.
Теперь возьмем тот же код, но при этом run будет принимать ссылку на string
.
void run(string const & v)
{
other(v);
}
void general(string str)
{
other(str);
//str используется, её нельзя перемещать в other
}
Да, Вы правы, передача ссылки в функцию run
- быстрая операция, можно считать, что это копирование указателя. Но у передачи ссылки есть и минусы. Один из них - функция run
обращается к строке через ссылку, т.е. появляется дополнительный уровень косвенности, который может вылиться в большие затраты, чем "лишнее" копирование указателя в версии функции, принимающей string_view
.
Теперь рассмотрим третий вариант функции run
:
void run(string v)
{
other(std::move(v));
}
В данном случае копирование строки происходит еще в функции general
(причем мы не перемещаем параметр str
, т.к. он где-то там еще нужен далее). Затем функция run
перемещает объект v
в параметр функции other
, а это в нашем случае приводит к копированию трех указателей и занулению старых. Очевидно, что в этом случае операций намного больше, чем со string_view
, и, вполне вероятно, что косвенное обращение будет тоже быстрее. То есть это может быть самым тормознутым вариантом.
А теперь представим, такую ситуацию:
void run(string const &); //<-- имеется такая run
void run(string_view); //или такая, нам без разницы
class SomeClass
{
string m_text;
mutex m_mutex;
void SomeClass::call()
{
//Мы знаем, что run делает копию строки,
//и запускает поток для её обработки
lock_quard<mutex> locker;//объект у нас защищен мьютексом
run(m_text);//Где-то там строка копируется и запускается новый поток
//мы не знаем точный момент, когда строка скопируется,
//поэтому вынуждены ждать выполнения run под защитой мьютекса,
//т.к. в other содержаться указатели на защищенные члены нашего класса.
//Вполне вероятно, что в этот момент другие
//потоки уже хотят работать с нашим объектом,
//но не могут, т.к. мьютекс захвачен.
}
};
Я в комментариях описал проблему. А теперь возьмем ситуацию с копированием и перемещением:
void run(string); //<-- теперь run принимает копию
class SomeClass
{
//..
void SomeClass::call()
{
//Мы знаем, что run принимает копию строки
//и запускает поток для её обработки
unique_lock<mutex> locker;//объект у нас защищен мьютексом
string text_copy = m_text;//Под защитой выполняем копирование строки
locker.unlock();//И разблокируем мьютекс, т.к.
run(std::move(text_copy));//нам уже без разницы что-том делает run,
//копия данных для него уже создана и другим потокам можно дать доступ к объекту
}
};
У string_view
при этом тоже имеются свои прелести. string_view
в принципе не привязан к string
- это просто данные и их размер. Т.е. string_view
может работать не только с string
:
void run(string_view v);
void general(string str)
{
other(str);//ok
}
void general(char const * str, size_t len)
{
other(string_view(str, len));//ok
}
void general(char const * str)
{
other(string_view(str));//ok
}
void general(std::vector<char> const & v)
{
other(string_view(v.data(), v.size()));//ok
}
В случае передачи ссылки придется сделать лишнюю копию строки, в случае передачи копии строки это не страшно, т.к. копия будет перемещена, но это всё равно может быть дороже. Однако, копия строки с перемещением может быть дешевле string_view
, если, например для создания string_view
потребуется сначала создать строку или какой-то другой буфер с данными. В таком случае будет лучше создать строку и переместить её.
Но оптимизации также могут перевернуть всё с ног на голову. Когда нужно сделать копию строки, кажется, что это приведет к аллокации памяти и всё это будет медленно. В общем случае это верно, но есть такая штука, как SSO (small string optimization), которая позволяет хранить маленькие строки прямо в объекте string
, используя сам объект как буфер для строки. В случае применения такой оптимизации копирование может оказаться равноценно или даже дешевле перемещения.
То есть создав копию в вызывающей функции и переместив строку в вызываемую функцию, Вы если и потеряете скорость, то очень немного. Но это всё относится к ситуации, когда нужна копия на вызываемой стороне. Если же копия не нужна, то вариантов также масса.
а для чего тогда стоит использовать string_view?
string_view
нужен как раз тогда, когда копия строки не нужна, но нужно сослаться на какое-то место и с ним работать как со строкой. Ранее нужно было либо писать свою "обертку" подобную string_view
, либо копировать нужные данные из исходной строки и работать с ними.
Всё сказанное выше весьма условно и служит только для демонстрации различных ситуаций.
Как видите, применение того или иного средства полностью зависит от конкретных условий использования, применяемых алгоритмов, оптимизаций компилятора, устройства библиотеки, платформы на которой всё это работает и т.д., поэтому закончу тем, с чего начал - использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий.
добавить комментарий |
Просто std::string
. А потом вместо компирования - перемещать через std::move
.
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чегоstring_view
не дает.
– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
добавить комментарий |
Если строка будет "просто скопирована", то вам следует реализовывать либо "ленивый" вариант семантики перемещения (передавать
std::string
по значению и затем делать из него перемещение), либо "полный" вариант семантики перемещения (писать две перегруженных функции: дляconst std::string &
и дляstd::string &&
), либо, возможно, реализовать forwarding (писать шаблонную функцию, принимающую универсальную сслыку и делающуюstd::forward
в вашу копию).
См. https://ru.stackoverflow.com/a/822789/182825
std::string_view
уместен везде, где вы будете просто анализировать строку, т.е. он является заменителемconst std:string &
в ситуациях, когда копирование не будет делаться.
добавить комментарий |
Ваш ответ
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "609"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "на платформе u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "Пользовательский контент попадает под действие u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003eлицензии cc by-sa 3.0u003c/au003e с u003ca href="https://stackoverflow.com/legal/content-policy"u003eуказанием ссылки на источникu003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Зарегистрируйтесь или войдите
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Регистрация через Google
Регистрация через Facebook
Регистрация через почту
Отправить без регистрации
Необходима, но никому не показывается
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fru.stackoverflow.com%2fquestions%2f954084%2fstdstring-vs-const-stdstring-vs-stdstring-view%23new-answer', 'question_page');
}
);
Отправить без регистрации
Необходима, но никому не показывается
3 ответа
3
текущие
по дате публикации
голоса
3 ответа
3
текущие
по дате публикации
голоса
текущие
по дате публикации
голоса
текущие
по дате публикации
голоса
Использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий. И это относится не только к std::string
и std::string_view
Для начала определимся с реализацией наших сущностей. Пусть std::string
построен на трех указателях по 4 байта каждый (или указатель и два размера):
class string
{
//...
pointer * m_begin; //Начало строки
pointer * m_end; //Указатель за последний элемент
pointer * m_end_of_storage; //Указатель за конец выделенной памяти
};
std::string_view
при этом реализован с помощью двух указателей (или указатель и размер):
class string_view
{
//...
pointer m_data; //Указатель на начало данных
pointer m_end; //Указатель за конец данных
};
Упрощенно рассмотрим три версии простого кода:
void other(std::string); //<-- функция, в которую всегда передается копия.
void run(string_view v) //<-- Функция, которая вызывает функцию other
{
//т.е. эта функция делает ту самую копию объекта
other(v);
}
void general(string str)
{
//А из этой функции в функцию run передается строка
other(str);
}
Что будет сгенерировано компилятором и как рассматривать не будем, т.к. это сильно зависит от компилятора и параметров компиляции, но постараемся посмотреть что может быть и как.
В нашем случае other
передает копию string_view
, т.е. general
копирует два указателя (не говорим о возможности вызова конструктора копирования, подразумевая, что он самый тривиальный и в результирующем коде приведет просто к копированию двух членов класса). Затем строка копируется из string_view
. Ничего сложного.
Теперь возьмем тот же код, но при этом run будет принимать ссылку на string
.
void run(string const & v)
{
other(v);
}
void general(string str)
{
other(str);
//str используется, её нельзя перемещать в other
}
Да, Вы правы, передача ссылки в функцию run
- быстрая операция, можно считать, что это копирование указателя. Но у передачи ссылки есть и минусы. Один из них - функция run
обращается к строке через ссылку, т.е. появляется дополнительный уровень косвенности, который может вылиться в большие затраты, чем "лишнее" копирование указателя в версии функции, принимающей string_view
.
Теперь рассмотрим третий вариант функции run
:
void run(string v)
{
other(std::move(v));
}
В данном случае копирование строки происходит еще в функции general
(причем мы не перемещаем параметр str
, т.к. он где-то там еще нужен далее). Затем функция run
перемещает объект v
в параметр функции other
, а это в нашем случае приводит к копированию трех указателей и занулению старых. Очевидно, что в этом случае операций намного больше, чем со string_view
, и, вполне вероятно, что косвенное обращение будет тоже быстрее. То есть это может быть самым тормознутым вариантом.
А теперь представим, такую ситуацию:
void run(string const &); //<-- имеется такая run
void run(string_view); //или такая, нам без разницы
class SomeClass
{
string m_text;
mutex m_mutex;
void SomeClass::call()
{
//Мы знаем, что run делает копию строки,
//и запускает поток для её обработки
lock_quard<mutex> locker;//объект у нас защищен мьютексом
run(m_text);//Где-то там строка копируется и запускается новый поток
//мы не знаем точный момент, когда строка скопируется,
//поэтому вынуждены ждать выполнения run под защитой мьютекса,
//т.к. в other содержаться указатели на защищенные члены нашего класса.
//Вполне вероятно, что в этот момент другие
//потоки уже хотят работать с нашим объектом,
//но не могут, т.к. мьютекс захвачен.
}
};
Я в комментариях описал проблему. А теперь возьмем ситуацию с копированием и перемещением:
void run(string); //<-- теперь run принимает копию
class SomeClass
{
//..
void SomeClass::call()
{
//Мы знаем, что run принимает копию строки
//и запускает поток для её обработки
unique_lock<mutex> locker;//объект у нас защищен мьютексом
string text_copy = m_text;//Под защитой выполняем копирование строки
locker.unlock();//И разблокируем мьютекс, т.к.
run(std::move(text_copy));//нам уже без разницы что-том делает run,
//копия данных для него уже создана и другим потокам можно дать доступ к объекту
}
};
У string_view
при этом тоже имеются свои прелести. string_view
в принципе не привязан к string
- это просто данные и их размер. Т.е. string_view
может работать не только с string
:
void run(string_view v);
void general(string str)
{
other(str);//ok
}
void general(char const * str, size_t len)
{
other(string_view(str, len));//ok
}
void general(char const * str)
{
other(string_view(str));//ok
}
void general(std::vector<char> const & v)
{
other(string_view(v.data(), v.size()));//ok
}
В случае передачи ссылки придется сделать лишнюю копию строки, в случае передачи копии строки это не страшно, т.к. копия будет перемещена, но это всё равно может быть дороже. Однако, копия строки с перемещением может быть дешевле string_view
, если, например для создания string_view
потребуется сначала создать строку или какой-то другой буфер с данными. В таком случае будет лучше создать строку и переместить её.
Но оптимизации также могут перевернуть всё с ног на голову. Когда нужно сделать копию строки, кажется, что это приведет к аллокации памяти и всё это будет медленно. В общем случае это верно, но есть такая штука, как SSO (small string optimization), которая позволяет хранить маленькие строки прямо в объекте string
, используя сам объект как буфер для строки. В случае применения такой оптимизации копирование может оказаться равноценно или даже дешевле перемещения.
То есть создав копию в вызывающей функции и переместив строку в вызываемую функцию, Вы если и потеряете скорость, то очень немного. Но это всё относится к ситуации, когда нужна копия на вызываемой стороне. Если же копия не нужна, то вариантов также масса.
а для чего тогда стоит использовать string_view?
string_view
нужен как раз тогда, когда копия строки не нужна, но нужно сослаться на какое-то место и с ним работать как со строкой. Ранее нужно было либо писать свою "обертку" подобную string_view
, либо копировать нужные данные из исходной строки и работать с ними.
Всё сказанное выше весьма условно и служит только для демонстрации различных ситуаций.
Как видите, применение того или иного средства полностью зависит от конкретных условий использования, применяемых алгоритмов, оптимизаций компилятора, устройства библиотеки, платформы на которой всё это работает и т.д., поэтому закончу тем, с чего начал - использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий.
добавить комментарий |
Использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий. И это относится не только к std::string
и std::string_view
Для начала определимся с реализацией наших сущностей. Пусть std::string
построен на трех указателях по 4 байта каждый (или указатель и два размера):
class string
{
//...
pointer * m_begin; //Начало строки
pointer * m_end; //Указатель за последний элемент
pointer * m_end_of_storage; //Указатель за конец выделенной памяти
};
std::string_view
при этом реализован с помощью двух указателей (или указатель и размер):
class string_view
{
//...
pointer m_data; //Указатель на начало данных
pointer m_end; //Указатель за конец данных
};
Упрощенно рассмотрим три версии простого кода:
void other(std::string); //<-- функция, в которую всегда передается копия.
void run(string_view v) //<-- Функция, которая вызывает функцию other
{
//т.е. эта функция делает ту самую копию объекта
other(v);
}
void general(string str)
{
//А из этой функции в функцию run передается строка
other(str);
}
Что будет сгенерировано компилятором и как рассматривать не будем, т.к. это сильно зависит от компилятора и параметров компиляции, но постараемся посмотреть что может быть и как.
В нашем случае other
передает копию string_view
, т.е. general
копирует два указателя (не говорим о возможности вызова конструктора копирования, подразумевая, что он самый тривиальный и в результирующем коде приведет просто к копированию двух членов класса). Затем строка копируется из string_view
. Ничего сложного.
Теперь возьмем тот же код, но при этом run будет принимать ссылку на string
.
void run(string const & v)
{
other(v);
}
void general(string str)
{
other(str);
//str используется, её нельзя перемещать в other
}
Да, Вы правы, передача ссылки в функцию run
- быстрая операция, можно считать, что это копирование указателя. Но у передачи ссылки есть и минусы. Один из них - функция run
обращается к строке через ссылку, т.е. появляется дополнительный уровень косвенности, который может вылиться в большие затраты, чем "лишнее" копирование указателя в версии функции, принимающей string_view
.
Теперь рассмотрим третий вариант функции run
:
void run(string v)
{
other(std::move(v));
}
В данном случае копирование строки происходит еще в функции general
(причем мы не перемещаем параметр str
, т.к. он где-то там еще нужен далее). Затем функция run
перемещает объект v
в параметр функции other
, а это в нашем случае приводит к копированию трех указателей и занулению старых. Очевидно, что в этом случае операций намного больше, чем со string_view
, и, вполне вероятно, что косвенное обращение будет тоже быстрее. То есть это может быть самым тормознутым вариантом.
А теперь представим, такую ситуацию:
void run(string const &); //<-- имеется такая run
void run(string_view); //или такая, нам без разницы
class SomeClass
{
string m_text;
mutex m_mutex;
void SomeClass::call()
{
//Мы знаем, что run делает копию строки,
//и запускает поток для её обработки
lock_quard<mutex> locker;//объект у нас защищен мьютексом
run(m_text);//Где-то там строка копируется и запускается новый поток
//мы не знаем точный момент, когда строка скопируется,
//поэтому вынуждены ждать выполнения run под защитой мьютекса,
//т.к. в other содержаться указатели на защищенные члены нашего класса.
//Вполне вероятно, что в этот момент другие
//потоки уже хотят работать с нашим объектом,
//но не могут, т.к. мьютекс захвачен.
}
};
Я в комментариях описал проблему. А теперь возьмем ситуацию с копированием и перемещением:
void run(string); //<-- теперь run принимает копию
class SomeClass
{
//..
void SomeClass::call()
{
//Мы знаем, что run принимает копию строки
//и запускает поток для её обработки
unique_lock<mutex> locker;//объект у нас защищен мьютексом
string text_copy = m_text;//Под защитой выполняем копирование строки
locker.unlock();//И разблокируем мьютекс, т.к.
run(std::move(text_copy));//нам уже без разницы что-том делает run,
//копия данных для него уже создана и другим потокам можно дать доступ к объекту
}
};
У string_view
при этом тоже имеются свои прелести. string_view
в принципе не привязан к string
- это просто данные и их размер. Т.е. string_view
может работать не только с string
:
void run(string_view v);
void general(string str)
{
other(str);//ok
}
void general(char const * str, size_t len)
{
other(string_view(str, len));//ok
}
void general(char const * str)
{
other(string_view(str));//ok
}
void general(std::vector<char> const & v)
{
other(string_view(v.data(), v.size()));//ok
}
В случае передачи ссылки придется сделать лишнюю копию строки, в случае передачи копии строки это не страшно, т.к. копия будет перемещена, но это всё равно может быть дороже. Однако, копия строки с перемещением может быть дешевле string_view
, если, например для создания string_view
потребуется сначала создать строку или какой-то другой буфер с данными. В таком случае будет лучше создать строку и переместить её.
Но оптимизации также могут перевернуть всё с ног на голову. Когда нужно сделать копию строки, кажется, что это приведет к аллокации памяти и всё это будет медленно. В общем случае это верно, но есть такая штука, как SSO (small string optimization), которая позволяет хранить маленькие строки прямо в объекте string
, используя сам объект как буфер для строки. В случае применения такой оптимизации копирование может оказаться равноценно или даже дешевле перемещения.
То есть создав копию в вызывающей функции и переместив строку в вызываемую функцию, Вы если и потеряете скорость, то очень немного. Но это всё относится к ситуации, когда нужна копия на вызываемой стороне. Если же копия не нужна, то вариантов также масса.
а для чего тогда стоит использовать string_view?
string_view
нужен как раз тогда, когда копия строки не нужна, но нужно сослаться на какое-то место и с ним работать как со строкой. Ранее нужно было либо писать свою "обертку" подобную string_view
, либо копировать нужные данные из исходной строки и работать с ними.
Всё сказанное выше весьма условно и служит только для демонстрации различных ситуаций.
Как видите, применение того или иного средства полностью зависит от конкретных условий использования, применяемых алгоритмов, оптимизаций компилятора, устройства библиотеки, платформы на которой всё это работает и т.д., поэтому закончу тем, с чего начал - использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий.
добавить комментарий |
Использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий. И это относится не только к std::string
и std::string_view
Для начала определимся с реализацией наших сущностей. Пусть std::string
построен на трех указателях по 4 байта каждый (или указатель и два размера):
class string
{
//...
pointer * m_begin; //Начало строки
pointer * m_end; //Указатель за последний элемент
pointer * m_end_of_storage; //Указатель за конец выделенной памяти
};
std::string_view
при этом реализован с помощью двух указателей (или указатель и размер):
class string_view
{
//...
pointer m_data; //Указатель на начало данных
pointer m_end; //Указатель за конец данных
};
Упрощенно рассмотрим три версии простого кода:
void other(std::string); //<-- функция, в которую всегда передается копия.
void run(string_view v) //<-- Функция, которая вызывает функцию other
{
//т.е. эта функция делает ту самую копию объекта
other(v);
}
void general(string str)
{
//А из этой функции в функцию run передается строка
other(str);
}
Что будет сгенерировано компилятором и как рассматривать не будем, т.к. это сильно зависит от компилятора и параметров компиляции, но постараемся посмотреть что может быть и как.
В нашем случае other
передает копию string_view
, т.е. general
копирует два указателя (не говорим о возможности вызова конструктора копирования, подразумевая, что он самый тривиальный и в результирующем коде приведет просто к копированию двух членов класса). Затем строка копируется из string_view
. Ничего сложного.
Теперь возьмем тот же код, но при этом run будет принимать ссылку на string
.
void run(string const & v)
{
other(v);
}
void general(string str)
{
other(str);
//str используется, её нельзя перемещать в other
}
Да, Вы правы, передача ссылки в функцию run
- быстрая операция, можно считать, что это копирование указателя. Но у передачи ссылки есть и минусы. Один из них - функция run
обращается к строке через ссылку, т.е. появляется дополнительный уровень косвенности, который может вылиться в большие затраты, чем "лишнее" копирование указателя в версии функции, принимающей string_view
.
Теперь рассмотрим третий вариант функции run
:
void run(string v)
{
other(std::move(v));
}
В данном случае копирование строки происходит еще в функции general
(причем мы не перемещаем параметр str
, т.к. он где-то там еще нужен далее). Затем функция run
перемещает объект v
в параметр функции other
, а это в нашем случае приводит к копированию трех указателей и занулению старых. Очевидно, что в этом случае операций намного больше, чем со string_view
, и, вполне вероятно, что косвенное обращение будет тоже быстрее. То есть это может быть самым тормознутым вариантом.
А теперь представим, такую ситуацию:
void run(string const &); //<-- имеется такая run
void run(string_view); //или такая, нам без разницы
class SomeClass
{
string m_text;
mutex m_mutex;
void SomeClass::call()
{
//Мы знаем, что run делает копию строки,
//и запускает поток для её обработки
lock_quard<mutex> locker;//объект у нас защищен мьютексом
run(m_text);//Где-то там строка копируется и запускается новый поток
//мы не знаем точный момент, когда строка скопируется,
//поэтому вынуждены ждать выполнения run под защитой мьютекса,
//т.к. в other содержаться указатели на защищенные члены нашего класса.
//Вполне вероятно, что в этот момент другие
//потоки уже хотят работать с нашим объектом,
//но не могут, т.к. мьютекс захвачен.
}
};
Я в комментариях описал проблему. А теперь возьмем ситуацию с копированием и перемещением:
void run(string); //<-- теперь run принимает копию
class SomeClass
{
//..
void SomeClass::call()
{
//Мы знаем, что run принимает копию строки
//и запускает поток для её обработки
unique_lock<mutex> locker;//объект у нас защищен мьютексом
string text_copy = m_text;//Под защитой выполняем копирование строки
locker.unlock();//И разблокируем мьютекс, т.к.
run(std::move(text_copy));//нам уже без разницы что-том делает run,
//копия данных для него уже создана и другим потокам можно дать доступ к объекту
}
};
У string_view
при этом тоже имеются свои прелести. string_view
в принципе не привязан к string
- это просто данные и их размер. Т.е. string_view
может работать не только с string
:
void run(string_view v);
void general(string str)
{
other(str);//ok
}
void general(char const * str, size_t len)
{
other(string_view(str, len));//ok
}
void general(char const * str)
{
other(string_view(str));//ok
}
void general(std::vector<char> const & v)
{
other(string_view(v.data(), v.size()));//ok
}
В случае передачи ссылки придется сделать лишнюю копию строки, в случае передачи копии строки это не страшно, т.к. копия будет перемещена, но это всё равно может быть дороже. Однако, копия строки с перемещением может быть дешевле string_view
, если, например для создания string_view
потребуется сначала создать строку или какой-то другой буфер с данными. В таком случае будет лучше создать строку и переместить её.
Но оптимизации также могут перевернуть всё с ног на голову. Когда нужно сделать копию строки, кажется, что это приведет к аллокации памяти и всё это будет медленно. В общем случае это верно, но есть такая штука, как SSO (small string optimization), которая позволяет хранить маленькие строки прямо в объекте string
, используя сам объект как буфер для строки. В случае применения такой оптимизации копирование может оказаться равноценно или даже дешевле перемещения.
То есть создав копию в вызывающей функции и переместив строку в вызываемую функцию, Вы если и потеряете скорость, то очень немного. Но это всё относится к ситуации, когда нужна копия на вызываемой стороне. Если же копия не нужна, то вариантов также масса.
а для чего тогда стоит использовать string_view?
string_view
нужен как раз тогда, когда копия строки не нужна, но нужно сослаться на какое-то место и с ним работать как со строкой. Ранее нужно было либо писать свою "обертку" подобную string_view
, либо копировать нужные данные из исходной строки и работать с ними.
Всё сказанное выше весьма условно и служит только для демонстрации различных ситуаций.
Как видите, применение того или иного средства полностью зависит от конкретных условий использования, применяемых алгоритмов, оптимизаций компилятора, устройства библиотеки, платформы на которой всё это работает и т.д., поэтому закончу тем, с чего начал - использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий.
Использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий. И это относится не только к std::string
и std::string_view
Для начала определимся с реализацией наших сущностей. Пусть std::string
построен на трех указателях по 4 байта каждый (или указатель и два размера):
class string
{
//...
pointer * m_begin; //Начало строки
pointer * m_end; //Указатель за последний элемент
pointer * m_end_of_storage; //Указатель за конец выделенной памяти
};
std::string_view
при этом реализован с помощью двух указателей (или указатель и размер):
class string_view
{
//...
pointer m_data; //Указатель на начало данных
pointer m_end; //Указатель за конец данных
};
Упрощенно рассмотрим три версии простого кода:
void other(std::string); //<-- функция, в которую всегда передается копия.
void run(string_view v) //<-- Функция, которая вызывает функцию other
{
//т.е. эта функция делает ту самую копию объекта
other(v);
}
void general(string str)
{
//А из этой функции в функцию run передается строка
other(str);
}
Что будет сгенерировано компилятором и как рассматривать не будем, т.к. это сильно зависит от компилятора и параметров компиляции, но постараемся посмотреть что может быть и как.
В нашем случае other
передает копию string_view
, т.е. general
копирует два указателя (не говорим о возможности вызова конструктора копирования, подразумевая, что он самый тривиальный и в результирующем коде приведет просто к копированию двух членов класса). Затем строка копируется из string_view
. Ничего сложного.
Теперь возьмем тот же код, но при этом run будет принимать ссылку на string
.
void run(string const & v)
{
other(v);
}
void general(string str)
{
other(str);
//str используется, её нельзя перемещать в other
}
Да, Вы правы, передача ссылки в функцию run
- быстрая операция, можно считать, что это копирование указателя. Но у передачи ссылки есть и минусы. Один из них - функция run
обращается к строке через ссылку, т.е. появляется дополнительный уровень косвенности, который может вылиться в большие затраты, чем "лишнее" копирование указателя в версии функции, принимающей string_view
.
Теперь рассмотрим третий вариант функции run
:
void run(string v)
{
other(std::move(v));
}
В данном случае копирование строки происходит еще в функции general
(причем мы не перемещаем параметр str
, т.к. он где-то там еще нужен далее). Затем функция run
перемещает объект v
в параметр функции other
, а это в нашем случае приводит к копированию трех указателей и занулению старых. Очевидно, что в этом случае операций намного больше, чем со string_view
, и, вполне вероятно, что косвенное обращение будет тоже быстрее. То есть это может быть самым тормознутым вариантом.
А теперь представим, такую ситуацию:
void run(string const &); //<-- имеется такая run
void run(string_view); //или такая, нам без разницы
class SomeClass
{
string m_text;
mutex m_mutex;
void SomeClass::call()
{
//Мы знаем, что run делает копию строки,
//и запускает поток для её обработки
lock_quard<mutex> locker;//объект у нас защищен мьютексом
run(m_text);//Где-то там строка копируется и запускается новый поток
//мы не знаем точный момент, когда строка скопируется,
//поэтому вынуждены ждать выполнения run под защитой мьютекса,
//т.к. в other содержаться указатели на защищенные члены нашего класса.
//Вполне вероятно, что в этот момент другие
//потоки уже хотят работать с нашим объектом,
//но не могут, т.к. мьютекс захвачен.
}
};
Я в комментариях описал проблему. А теперь возьмем ситуацию с копированием и перемещением:
void run(string); //<-- теперь run принимает копию
class SomeClass
{
//..
void SomeClass::call()
{
//Мы знаем, что run принимает копию строки
//и запускает поток для её обработки
unique_lock<mutex> locker;//объект у нас защищен мьютексом
string text_copy = m_text;//Под защитой выполняем копирование строки
locker.unlock();//И разблокируем мьютекс, т.к.
run(std::move(text_copy));//нам уже без разницы что-том делает run,
//копия данных для него уже создана и другим потокам можно дать доступ к объекту
}
};
У string_view
при этом тоже имеются свои прелести. string_view
в принципе не привязан к string
- это просто данные и их размер. Т.е. string_view
может работать не только с string
:
void run(string_view v);
void general(string str)
{
other(str);//ok
}
void general(char const * str, size_t len)
{
other(string_view(str, len));//ok
}
void general(char const * str)
{
other(string_view(str));//ok
}
void general(std::vector<char> const & v)
{
other(string_view(v.data(), v.size()));//ok
}
В случае передачи ссылки придется сделать лишнюю копию строки, в случае передачи копии строки это не страшно, т.к. копия будет перемещена, но это всё равно может быть дороже. Однако, копия строки с перемещением может быть дешевле string_view
, если, например для создания string_view
потребуется сначала создать строку или какой-то другой буфер с данными. В таком случае будет лучше создать строку и переместить её.
Но оптимизации также могут перевернуть всё с ног на голову. Когда нужно сделать копию строки, кажется, что это приведет к аллокации памяти и всё это будет медленно. В общем случае это верно, но есть такая штука, как SSO (small string optimization), которая позволяет хранить маленькие строки прямо в объекте string
, используя сам объект как буфер для строки. В случае применения такой оптимизации копирование может оказаться равноценно или даже дешевле перемещения.
То есть создав копию в вызывающей функции и переместив строку в вызываемую функцию, Вы если и потеряете скорость, то очень немного. Но это всё относится к ситуации, когда нужна копия на вызываемой стороне. Если же копия не нужна, то вариантов также масса.
а для чего тогда стоит использовать string_view?
string_view
нужен как раз тогда, когда копия строки не нужна, но нужно сослаться на какое-то место и с ним работать как со строкой. Ранее нужно было либо писать свою "обертку" подобную string_view
, либо копировать нужные данные из исходной строки и работать с ними.
Всё сказанное выше весьма условно и служит только для демонстрации различных ситуаций.
Как видите, применение того или иного средства полностью зависит от конкретных условий использования, применяемых алгоритмов, оптимизаций компилятора, устройства библиотеки, платформы на которой всё это работает и т.д., поэтому закончу тем, с чего начал - использование любого из альтернативных средств очень сильно зависит от ситуации, и выбирается исходя из конкретных условий.
изменён 9 часов назад
ответ дан 10 часов назад
CroessmahCroessmah
3,973717
3,973717
добавить комментарий |
добавить комментарий |
Просто std::string
. А потом вместо компирования - перемещать через std::move
.
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чегоstring_view
не дает.
– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
добавить комментарий |
Просто std::string
. А потом вместо компирования - перемещать через std::move
.
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чегоstring_view
не дает.
– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
добавить комментарий |
Просто std::string
. А потом вместо компирования - перемещать через std::move
.
Просто std::string
. А потом вместо компирования - перемещать через std::move
.
ответ дан 12 часов назад
HolyBlackCatHolyBlackCat
5,6041514
5,6041514
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чегоstring_view
не дает.
– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
добавить комментарий |
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чегоstring_view
не дает.
– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
а для чего тогда стоит использовать string_view? И чем это лучше чем константная ссылка, с ней то по идее будет вызван только copy конструктор
– tim bars
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
это всё весьма и весьма сомнительно.
– Croessmah
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).
string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чего string_view
не дает.– HolyBlackCat
12 часов назад
@timbars "чем это лучше" Тем, что при таком варианте мы можем в некоторых случаях избежать копирования - когда передаем в функцию временный объект (rvalue).
string_view
, имхо, нужен для функций, анализирующих строки или сегменты строк (при парсинге и т. п.). Как универсальный "параметр-строка" он не особо прижился, имхо, потому что часто требудется, чтобы параметр был null-terminated, чего string_view
не дает.– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
@Croessmah Расска́жите подробнее?
– HolyBlackCat
12 часов назад
добавить комментарий |
Если строка будет "просто скопирована", то вам следует реализовывать либо "ленивый" вариант семантики перемещения (передавать
std::string
по значению и затем делать из него перемещение), либо "полный" вариант семантики перемещения (писать две перегруженных функции: дляconst std::string &
и дляstd::string &&
), либо, возможно, реализовать forwarding (писать шаблонную функцию, принимающую универсальную сслыку и делающуюstd::forward
в вашу копию).
См. https://ru.stackoverflow.com/a/822789/182825
std::string_view
уместен везде, где вы будете просто анализировать строку, т.е. он является заменителемconst std:string &
в ситуациях, когда копирование не будет делаться.
добавить комментарий |
Если строка будет "просто скопирована", то вам следует реализовывать либо "ленивый" вариант семантики перемещения (передавать
std::string
по значению и затем делать из него перемещение), либо "полный" вариант семантики перемещения (писать две перегруженных функции: дляconst std::string &
и дляstd::string &&
), либо, возможно, реализовать forwarding (писать шаблонную функцию, принимающую универсальную сслыку и делающуюstd::forward
в вашу копию).
См. https://ru.stackoverflow.com/a/822789/182825
std::string_view
уместен везде, где вы будете просто анализировать строку, т.е. он является заменителемconst std:string &
в ситуациях, когда копирование не будет делаться.
добавить комментарий |
Если строка будет "просто скопирована", то вам следует реализовывать либо "ленивый" вариант семантики перемещения (передавать
std::string
по значению и затем делать из него перемещение), либо "полный" вариант семантики перемещения (писать две перегруженных функции: дляconst std::string &
и дляstd::string &&
), либо, возможно, реализовать forwarding (писать шаблонную функцию, принимающую универсальную сслыку и делающуюstd::forward
в вашу копию).
См. https://ru.stackoverflow.com/a/822789/182825
std::string_view
уместен везде, где вы будете просто анализировать строку, т.е. он является заменителемconst std:string &
в ситуациях, когда копирование не будет делаться.
Если строка будет "просто скопирована", то вам следует реализовывать либо "ленивый" вариант семантики перемещения (передавать
std::string
по значению и затем делать из него перемещение), либо "полный" вариант семантики перемещения (писать две перегруженных функции: дляconst std::string &
и дляstd::string &&
), либо, возможно, реализовать forwarding (писать шаблонную функцию, принимающую универсальную сслыку и делающуюstd::forward
в вашу копию).
См. https://ru.stackoverflow.com/a/822789/182825
std::string_view
уместен везде, где вы будете просто анализировать строку, т.е. он является заменителемconst std:string &
в ситуациях, когда копирование не будет делаться.
ответ дан 9 часов назад
AnTAnT
50.2k33895
50.2k33895
добавить комментарий |
добавить комментарий |
Спасибо за ваш ответ на Stack Overflow на русском!
- Пожалуйста, убедитесь, что публикуемое сообщение отвечает на поставленный вопрос. Предоставьте как можно больше деталей, расскажите про проведенное исследование!
Но избегайте …
- Просьб помощи, уточнений или ответов на темы не относящиеся к вопросу.
- Ответов основанных на мнениях; приводите аргументы основанные только на реальном опыте.
Также, обратите внимание на заметку в справочном центре о том, как писать ответы.
Зарегистрируйтесь или войдите
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Регистрация через Google
Регистрация через Facebook
Регистрация через почту
Отправить без регистрации
Необходима, но никому не показывается
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fru.stackoverflow.com%2fquestions%2f954084%2fstdstring-vs-const-stdstring-vs-stdstring-view%23new-answer', 'question_page');
}
);
Отправить без регистрации
Необходима, но никому не показывается
Зарегистрируйтесь или войдите
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Регистрация через Google
Регистрация через Facebook
Регистрация через почту
Отправить без регистрации
Необходима, но никому не показывается
Зарегистрируйтесь или войдите
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Регистрация через Google
Регистрация через Facebook
Регистрация через почту
Отправить без регистрации
Необходима, но никому не показывается
Зарегистрируйтесь или войдите
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Регистрация через Google
Регистрация через Facebook
Регистрация через почту
Регистрация через Google
Регистрация через Facebook
Регистрация через почту
Отправить без регистрации
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается
Необходима, но никому не показывается