Можно ли добавить std::string в себя?
Учитывая такой код:
std::string str = "abcdef";
const size_t num = 50;
const size_t baselen = str.length();
while (str.length() < num)
str.append(str, 0, baselen);
Безопасно ли вызывать std::basic_string<T>::append()
как таковое? Не удается ли потерять память источника, увеличив ее до операции копирования?
Я не мог найти ничего в стандарте, специфичном для этого метода. Он говорит, что приведенное выше эквивалентно str.append(str.data(), baselen)
, что, я думаю, может быть не совсем безопасным, если не будет обнаружение таких случаев внутри append(const char*, size_t)
.
Я проверил несколько реализаций, и они казались безопасными так или иначе, но мой вопрос в том, гарантируется ли это поведение. Например. "Добавление std::vector к себе, undefined поведение?" говорит об этом не для std::vector
.
Ответы
Ответ 1
Согласно § 21.4.6.2/§21.4.6.3:
Функция [ basic_string& append(const charT* s, size_type n);
] заменяет строку, контролируемую * этим, строкой length size() + n, чьи первые элементы size() являются копией исходной строки, контролируемой * этот и оставшиеся элементы которого являются копией начальных n элементов s.
Примечание. Это относится ко всем вызовам append
, так как каждый append
может быть реализован в терминах append(const charT*, size_type)
, как определено стандартом (§21.4.6.2/§21.4.6.3).
Таким образом, append
делает копию str
(позвоните по вызову strtemp
), добавляет n
символы от str2
до strtemp
, а затем заменяет str
на strtemp
.
В случае, когда str2
является str
, ничто не изменяется, поскольку строка увеличивается при назначении временной копии, а не раньше.
Несмотря на то, что это не указано явно в стандарте, оно гарантируется (если реализация в точности соответствует заявленной в стандарте) по определению std::basic_string<T>::append
.
Таким образом, это не поведение undefined.
Ответ 2
Это сложно.
Одно можно сказать наверняка. Если вы используете итераторы:
std::string str = "abcdef";
str.append(str.begin(), str.end());
тогда вы гарантированно будете в безопасности. Да, действительно. Зачем? Поскольку в спецификации указано, что поведение функций итератора эквивалентно вызову append(basic_string(first, last))
. Это, очевидно, создает временную копию строки. Поэтому, если вам нужно вставить строку в себя, вы гарантированно сможете сделать это с помощью формы итератора.
Конечно, реализациям не нужно ее копировать. Но они должны соблюдать стандартное поведение. Реализация может сделать копию только в том случае, если диапазон итератора находится внутри себя, но реализация все равно должна быть проверена.
Все остальные формы append
определены как эквивалентные вызову append(const charT *s, size_t len)
. То есть ваш призыв к приложению выше эквивалентен тому, что вы делаете append(str.data(), str.size())
. Итак, что говорит стандарт о том, что произойдет, если s
находится внутри *this
?
Ничего.
Единственное требование s
:
s
указывает на массив не менее n
элементов charT
.
Так как он явно не запрещает s
указывать на *this
, то он должен быть разрешен. Было бы также чрезвычайно странно, если версия итератора допускает самозадачу, но версия указателя и размера не имеет значения.