"" + что-то в С++

У меня был настоящий причудливый материал в моем коде. Я считаю, что я отследил его до части с надписью "здесь" (код, конечно же, упрощен):

std::string func() {
    char c;
    // Do stuff that will assign to c
    return "" + c; // Here
}

Всевозможные вещи произойдут, когда я попытаюсь выполнить cout результат этой функции. Я думаю, мне даже удалось получить куски лежащей в основе документации на С++ и многие segmentation fault. Мне ясно, что это не работает на С++ (я прибегал к использованию stringstream для преобразования в string), но я хотел бы знать, почему. После использования большого количества С# довольно долгое время и без С++ это вызвало у меня большую боль.

Ответ 1

  • "" - строковый литерал. У них есть массив типов N const char. Этот конкретный строковый литерал представляет собой массив из 1 const char, один из которых является нулевым терминатором.

  • Массивы легко распадаются на указатели на их первый элемент, например. в выражениях, где требуется указатель.

  • lhs + rhs не определен для массивов как lhs и целых чисел как rhs. Но он определен для указателей как lhs и integers как rhs, с обычной арифметикой указателя.

  • char является интегральным типом данных в (то есть, рассматривается как целое) на языке ядра С++.

== > string literal + character поэтому интерпретируется как указатель + integer.

Выражение "" + c примерно эквивалентно выражению:

static char const lit[1] = {'\0'};
char const* p = &lit[0];
p + c // "" + c is roughly equivalent to this expression

Вы возвращаете std::string. Выражение "" + c дает указатель на const char. Конструктор std::string, ожидающий, что const char* ожидает, что он будет указателем на массив символов с нулевым символом.

Если c != 0, то выражение "" + c приводит к Undefined Поведение:

  • Для c > 1 арифметика указателя создает Undefined Поведение. Арифметика указателя определяется только для массивов, и если результат является элементом одного и того же массива.

  • Если char подписывается, то c < 0 создает Undefined Поведение по той же причине.

  • Для c == 1 арифметика указателя не создает Undefined Поведение. Это особый случай; указывая на один элемент, прошедший последний элемент массива, разрешен (хотя ему не разрешено использовать то, на что он указывает). Это по-прежнему приводит к Undefined Behavior, поскольку вызываемый здесь конструктор std::string требует, чтобы его аргумент был указателем на допустимый массив (и строка с завершающим нулем). Элемент "один за прошлым" не является частью самого массива. Нарушение этого требования также приводит к UB.


Что теперь происходит, так это то, что конструктор std::string пытается определить размер строки с завершающим нулем, которую вы передали, путем поиска (первого) символа в массиве, который равен '\0':

string(char const* p)
{
    // simplified
    char const* end = p;
    while(*end != '\0') ++end;
    //...
}

это приведет либо к нарушению доступа, либо к строке, которая она создает, содержит "мусор". Также возможно, что компилятор предполагает, что этот Undefined Поведение никогда не произойдет, и делает некоторые смешные оптимизации, которые приведут к странному поведению.


Кстати, clang++ 3.5 выдает приятное предупреждение для этого фрагмента:

предупреждение: добавление 'char' к строке не добавляется к строке [-Wstring-плюс-INT]

return "" + c; // Here
       ~~~^~~

Примечание: используйте индексирование массива, чтобы отключить это предупреждение.

Ответ 2

Есть много объяснений того, как компилятор интерпретирует этот код, но то, что вы, вероятно, хотели знать, - это то, что вы сделали неправильно.

Кажется, вы ожидаете поведения + от std::string. Проблема в том, что ни один из операндов на самом деле не является std::string. С++ рассматривает типы операндов, а не конечный тип выражения (здесь тип возврата, std::string), чтобы разрешить перегрузку. Он не будет выбирать std::string версию +, если не видит std::string.

Если у вас есть специальное поведение для оператора (либо вы его написали, либо получили библиотеку, которая его предоставляет), это поведение применимо только тогда, когда хотя бы один из операндов имеет тип класса (или ссылку на тип класса и пользовательские настройки) определенные подсчеты перечислений).

Если вы написали

std::string("") + c

или

std::string() + c

или

""s + c // requires C++14

тогда вы получите поведение std::string оператора +.

(Обратите внимание: ни одно из них на самом деле не является хорошим решением, потому что все они делают недолговечные экземпляры std::string, которых можно избежать с помощью std::string(1, c))

То же самое касается функций. Вот пример:

std::complex<double> ipi = std::log(-1.0);

Вы получите ошибку времени выполнения вместо ожидаемого мнимого числа. Это потому, что компилятор не знает, что здесь должен использоваться сложный логарифм. Перегрузка смотрит только на аргументы, и аргумент является реальным числом (тип double, фактически).

Оператор перегружает функции ARE и подчиняется тем же правилам.

Ответ 3

Этот оператор возврата

return "" + c;

. Используется так называемая арифметика указателя. Строковый литерал "" преобразуется в указатель на его первый символ (в этом случае до его нулевого окончания), а целочисленное значение, хранящееся в c, добавляется к указателю. Таким образом, результат выражения

"" + c

имеет тип const char *

Класс std::string имеет конструктор преобразования, который принимает аргумент типа const char *. Проблема в том, что этот указатель может указывать на строку, отличную от строкового литерала. Таким образом, функция имеет поведение undefined.

Я не вижу смысла в использовании этого выражения. Если вы хотите построить строку на основе одного символа, вы можете написать, например,

return std::string( 1, c );

разница между С++ и С# заключается в том, что в строковых литералах С# есть тип System.String, который перегрузил operator + для строк и символов (которые являются символами Unicode на С#). В С++ строковые литералы являются постоянными массивами символов, а семантика оператора + для массивов и целых чисел различна. Массивы преобразуются в указатели на их первые элементы и используются арифметика указателя.

Это стандартный класс std::string, который перегрузил оператор + для символов. Строковые литералы в С++ не являются объектами этого класса, которые имеют тип std::string.