""+ что-то в C++


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

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

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

3 59

3 ответа:

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

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

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

  • char - это интегральный тип данных (т. е. обрабатывается как целое число) на языке ядра C++.

==>строковый литерал + символ трактуется как указатель + целое.

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

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

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

если c != 0, то выражение "" + c приводит к неопределенному поведению:

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

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

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


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

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

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


кстати, clang++3.5 выдает хорошее предупреждение на этот фрагмент:

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

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

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

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

Вы, кажется, ожидаете + поведение std::string. Проблема в том, что ни один из операндов на самом деле это std::string. C++ смотрит на типы операндов, а не на конечный тип выражения (здесь возвращаемый тип, std::string) для устранения перегрузки. Он не будет выбирать std::string'S версия + если он не видит 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, фактически.)

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

этот оператор return

return "" + c;

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

"" + c

типа const char *

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

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

return std::string( 1, c );

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

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