Мне нужно создать char[] динамически. Я хотел бы использовать std::string вместо работы с char[] напрямую, так как мне не нужно заботиться об управлении памятью с помощью std::string. Вот мой пример кода:
int main()
{
std::vector<char*> char_vec;
for(size_t i = 0; i < 10; i++)
{
std::string s;
s = "test" + std::to_string(i);
char_vec.push_back(const_cast<char*>(s.c_str()));
}
for(auto& ele : char_vec)
{
std::cout << ele << std::endl;
}
}
Результат: , чего я не хочу.
В моем понимании строка s, созданная внутри цикла, должна быть совершенно новой на каждой итерации, но на самом деле все строки используют один и тот же адрес для хранения содержимого. Почему это происходит? Как мне изменить свой код для достижения моей цели?
Спасибо за помощь!
Обновление от 10 января 2023 г.:
Я попробовал следующий код на основе ответов:
int main()
{
std::vector<char*> char_vec;
std::vector<std::string> str_vec;
for(size_t i = 0; i < 10; i++)
{
// str_vec.emplace_back("test" + std::to_string(i));
str_vec.push_back("test" + std::to_string(i));
char_vec.push_back(const_cast<char*>(str_vec.back().c_str()));
}
for(auto& ele : char_vec)
{
std::cout << ele << std::endl;
}
}
но все еще не может вывести желаемые результаты. Могу я спросить, почему это не работает?
На каждой итерации цикла создается временная переменная std::string
. Затем вы берете указатель на данные, которыми управляет переменная, и помещаете его в вектор. Затем переменная уничтожается, как и управляемые ею данные. В конце у вас есть вектор, набитый болтающимися указателями. Попытка использовать эти указатели впоследствии приводит к неопределенному поведению.
@TedLyngmo Потому что (вероятно) SSO
@PaulSanders Да - и это, вероятно, спасло ОП от мира боли позже. Если бы были созданы более длинные строки, OP, вероятно, подумал бы, что все в порядке.
Сохранение указателей s.c_str()
в векторе, когда s
живет только до конца итерации цикла, приведет к неопределенному поведению, самое позднее, когда вы попытаетесь прочитать эти указатели.
Неважно, имеют ли указатели одинаковое значение. Как только объем s
заканчивается, они становятся недействительными. Память, к которой они обращаются, может быть повторно использована компилятором/библиотекой для чего угодно. Вы не гарантируете, что вообще сможете прочесть из них что-то толковое. То, что ты отыгрался test9
, было "удачей".
Вы должны использовать std::vector<std::string>
, а затем просто char_vec.push_back(s);
(или, если вы хотите оптимизировать char_vec.push_back(std::move(s))
, но только потому, что вы не используете s
после push_back
).
Или еще лучше char_vec.push_back(std::move(s));
@user17732522 user17732522 Я обновил код на основе вашего предложения, но все еще не могу получить желаемые результаты. Могу ли я спросить, почему?
@john, могу я спросить, почему std::move() лучше?
Потому что это экономит затраты на копирование строки. Поскольку вы больше не используете переменную s
, память, на которую она ссылается, может быть просто «перемещена» в вектор без необходимости выделения новой памяти и копирования символов.
@ChuanXu std::vector
хранит свои элементы непрерывно. Если вы добавляете новый элемент и зарезервированный блок памяти становится слишком маленьким, то std::vector
должен сделать новое (большее) выделение и скопировать/переместить в него старые элементы. Это делает недействительным любой указатель, который вы получили в std::string
s. Если вам нужно полагаться на то, что указатели char*
остаются действительными, пока вектор действителен, то вы должны либо сначала вызвать .reserve(10)
для вектора и убедиться, что его размер никогда не превышает 10
, либо вам нужно использовать другой контейнер, например. std::list
.
@ChuanXu Или, лучше, сформируйте вектор char*
только после того, как вы закончите изменение вектора std::string
. Более того, старайтесь избегать формирования массива таких указателей char*
в первую очередь. Обычно вам это не нужно в современном С++. Но если вам это нужно (например, потому что вам нужно вызвать C API), тогда охватите вектор char*
как можно более узко.
@user17732522 user17732522 Круто, твое объяснение имеет смысл. Спасибо.
Каждый
std::string
, созданный в цикле, создается точно в одном и том же месте памяти. Обратите внимание, что сохраненные вами строки C исчезают после цикла for, когдаs
выходит за рамки. Поэтому программа имеет неопределенное поведение.