Возвращение std :: string через тернарный оператор - возвращает мусор - PullRequest
0 голосов
/ 24 февраля 2020

У меня следующий код:

#include <string>
#include <cstdio>

std::string name = "Ternary Return Test";
std::string *pname = &name;

const std::string &getName ()
{
    return pname ? *pname : "(unnamed)";
}

int main (int argc, char *argv[])
{
    const std::string &str = getName();
    printf ("Name is \"%s\"\n",str.c_str());
    printf ("pName is \"%s\"\n",pname->c_str());
    return 0; 
}

Функция getName () разыменовывает указатель pname и возвращает его значение в качестве ссылки. Как я понимаю, возвращение ссылки на что-либо через разыменование указателя вполне допустимо. Чтобы избежать разыменования указателя NULL, функция возвращает строку «без имени», в случае (в этом случае невозможном), когда указатель pname имеет значение NULL.

Однако троичный оператор прерывает функцию. Код компилируется, но печатает следующее:

Name is "�
          @"
pName is "Ternary Return Test"

Значение, полученное с помощью функции, не работает. Я не могу понять почему. В другой ситуации подобный код вызывает ошибку сегментации. Итак, здесь происходит неопределенное поведение?

Да, в случае, когда pname будет иметь значение NULL, оно вернет ссылку на временный объект, который не определен. Но это не так - я возвращаю ссылку на невременное значение, которое должно быть совершенно корректным.

Как я прочитал, троичный оператор преобразует третье выражение "(без имени)" в тип второй (std :: string). Но в моем случае третье выражение даже не используется.

Ответы [ 4 ]

4 голосов
/ 24 февраля 2020

Но это не тот случай - я возвращаю ссылку на временное значение, которое должно быть совершенно правильным.

Вы бы, но у вас нет больше не временное значение. [expr.cond] /4.3 имеет

Если E2 является prvalue или если ни одна из приведенных выше последовательностей преобразования не может быть сформирована, и хотя бы один из операндов имеет (возможно, cv -квалифицированный) тип класса:

  • , если T1 и T2 относятся к одному и тому же типу класса (игнорируя квалификацию cv), а T2, по крайней мере, так же квалифицирован для cv, что и T1, целевой тип - T2,

И что сводится к тому, что, поскольку третий операнд является prvalue, все выражение является prvalue, что означает, что у вас всегда есть неопределенное поведение, поскольку вы всегда возвращаете ссылку на временное значение.

3 голосов
/ 24 февраля 2020

тип троичного оператора для

pname ? *pname : "(unnamed)" равен std::string (у нас есть std::string& и const char*)

Так что это эквивалентно pname ? std::string{*pname} : std::string{"(unnamed)"}.

Таким образом, вы возвращаете ссылку из временной переменной ( в обоих случаях ) (и, таким образом, висящую ссылку, ведущую к UB при ее использовании).

Если вы использовали if вместо троичный, у вас будет ожидаемая ошибка

const std::string& getName()
{
    if pname { return *pname; } // OK here
    return "(unnamed)"; // Dangling pointer here
}

Одно из возможных решений:

const std::string &getName ()
{
    static const std::string unnamed = "(unnamed)";
    return pname ? *pname : unnamed;
}

Таким образом, обе стороны являются lvalue, а общий тип теперь const std::string & (std::string & и const std::string&).

1 голос
/ 24 февраля 2020

Как указывает Иксисарвинен, проблема - это проблема на всю жизнь. "(unnamed)" имеет stati c срок хранения, но это не то, что вы возвращаете. Вы возвращаете std::string{"(unnamed)"} и ссылку, если быть точным. Это безымянное временное значение исчезает до того, как функция возвращается.

Исправлено:

std::string const& getName ()
{
    static const std::string defaultValue = "(unnamed)";
    return pname ? *pname : defaultValue;
}
0 голосов
/ 24 февраля 2020

Для справки (висячий эталонный UB уже хорошо описан), это, вероятно, должно быть что-то вроде

const std::string &getName ()
{
    static const std::string unnamed("unnamed");
    return pname ? *pname : unnamed;
}
...