2015-01-13 2 views
3

Рассмотрим следующий код:Почему моя переменная-член строки задана пустой строкой в ​​C++?

class Foo 
{ 
private: 
    const string& _bar; 

public: 
    Foo(const string& bar) 
     : _bar(bar) { } 

    const string& GetBar() { return _bar; } 
}; 

int main() 
{ 
    Foo foo1("Hey"); 
    cout << foo1.GetBar() << endl; 

    string barString = "You"; 
    Foo foo2(barString); 
    cout << foo2.GetBar() << endl; 
} 

Когда я выполняю этот код (в VS 2013), экземпляр foo1 имеет пустую строку в ее переменной _bar член в то время как соответствующая переменная член foo2 «s содержит ссылку на значение" Вы". Почему?

Обновление: Я, конечно, использую класс std :: string в этом примере.

+4

Потому что первый указывает на временную строку, которая была уничтожена сразу после вызова конструктора Foo. (Временная std :: строка, созданная из (c-) строкового литерала «Hey») – Borgleader

+2

Просто указывая на это, если вы используете 'const char *', первый случай будет в порядке, поскольку строковые литералы живут вечно. –

+0

Btw, если ваш конструктор принимал неконстантный параметр, первая версия не скомпилировалась, потому что вы не можете привязать временный объект к неконстантическому ref. – Borgleader

ответ

10

Для Foo foo1("Hey") компилятор должен выполнить преобразование с const char[4] по адресу std::string. Он создает prvalue типа std::string. Эта линия эквивалентна:

Foo foo1(std::string("Hey")); 

Ссылка связывания происходит от prvalue к bar, а затем еще одна ссылка связывания происходит от bar к Foo::_bar. Проблема здесь в том, что std::string("Hey") является временным, который уничтожается, когда полное выражение, в котором оно появляется, заканчивается. То есть после точки с запятой std::string("Hey") не будет существовать.

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

Линия Foo foo2(barString) нормально, потому что barString существует после инициализации foo2, поэтому Foo::_bar все еще указывает на действительный экземпляр std::string. Временное создание не создается, потому что тип инициализатора соответствует типу ссылки.

+0

Благодарим вас за подробное объяснение - очевидно, что многое происходит, чего я еще не полностью понял, особенно prvalue и reference bind. Я займусь этим. – feO2x

7

Вы принимаете ссылку на объект, который разрушается в конце строки с помощью foo1. В foo2 объект barString все еще существует, поэтому ссылка остается в силе.

2

Да, это чудеса C++ и понимание:

  1. Время жизни объектов
  2. Эта строка является классом, а буквальное символьные массивы не являются «строками».
  3. Что происходит с неявными конструкторами.

В любом случае строка является классом, «Hey» на самом деле представляет собой просто массив символов. Поэтому, когда вы создаете Foo с «Hey», который хочет ссылку на строку, он выполняет так называемое неявное преобразование. Это происходит потому, что string имеет неявный конструктор из массивов символов.

Теперь на время жизни объекта. Построив эту цепочку для вас, где она живет и какова ее продолжительность жизни. Ну собственно для значения этого вызова, вот конструктор Foo и все, что он называет. Таким образом, он может вызывать всевозможные функции целиком и эта строка действительна.

Однако, как только этот вызов завершен, объект истекает.К сожалению, вы сохранили в своем классе ссылку на const, и вам разрешено. Компилятор не жалуется, потому что вы можете сохранить ссылку на const для объекта, который будет жить дольше.

К сожалению, это неприятная ловушка. И я помню, как только я преднамеренно отдал своего конструктора, который действительно хотел ссылку на const, неконстантную ссылку с целью обеспечить точно, чтобы эта ситуация не возникала (и не получала бы временную). Возможно, это не лучший способ обхода, но он работал в то время.

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

Вы также можете взять неконстантную ссылку на строку и «своп» это в

С C++ 11 есть еще один вариант использования перемещения семантики, что означает, что строка, переданная в станет " приобретенный ", сам признал недействительным. Это особенно полезно, когда вы хотите использовать временные ряды, которые являются вашим примером (хотя главным образом временные строятся с помощью явного конструктора или возвращаемого значения).

2

Проблема заключается в том, что в этом коде:

Foo foo1("Hey"); 

Из строки буквального "Hey" (сырой char массив, точнее const char [4], принимая во внимание три символа в Hey и завершающий \0) a Временныйstd::string экземпляр создается, и он передается в конструктор Foo(const string&).

Этот конструктор сохраняет ссылки этой временной строки в элемент const string& _bar данных:

Foo(const string& bar) 
     : _bar(bar) { } 

Теперь проблема заключается в том, что вы сохраняете ссылки на временный строка. Поэтому, когда временная строка «испаряется» (после оператора вызова конструктора), эта ссылка становится dangling, то есть она ссылается («указывает на ...») некоторый мусор.
Итак, вы понесли в неопределенное поведение (например, компиляция вашего кода с помощью MinGW в Windows с g ++, у меня другой результат).

Вместо этого во втором случае:

string barString = "You"; 
Foo foo2(barString); 

ваш foo2::_bar ссылка связана с ("указывает на") barString, который не временным, но местный переменная в main().Итак, после вызова конструктора barString все еще существует, когда вы печатаете строку, используя cout << foo2.GetBar().

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