Константная ссылка на временный объект перестает работать после области действия функции (время жизни)
Задавая этот вопрос, я узнал, что постоянная ссылка на временный объект действительна в C++:
int main ()
{
int a = 21;
int b = 21;
//error: invalid initialization of non-const reference
//int & sum = a + b;e [...]
//OK
int const & sum = a + b;
return sum;
}
Но в следующем примере константная ссылка refnop
относится к разрушенному временному объекту. Интересно, почему?
#include <string>
#include <map>
struct A
{
// data
std::map <std::string, std::string> m;
// functions
const A& nothing() const { return *this; }
void init() { m["aa"] = "bb"; }
bool operator!= (A const& a) const { return a.m != m; }
};
int main()
{
A a;
a.init();
A const& ref = A(a);
A const& refnop = A(a).nothing();
int ret = 0;
if (a != ref) ret += 2;
if (a != refnop) ret += 4;
return ret;
}
Протестировано с использованием GCC 4.1.2 и MSVC 2010, возвращается 4;
$> g++ -g refnop.cpp
$> ./a.out ; echo $?
4
Разница между ref
а также refnop
это призыв к nothing()
который действительно ничего не делает. Кажется, после этого вызова временный объект уничтожен!
Мой вопрос:
Почему в случае refnop
время жизни временного объекта не совпадает с его постоянной ссылкой?
2 ответа
Продление срока жизни временного объекта может быть выполнено только один раз, когда временный объект привязывается к первой ссылке. После этого знание о том, что ссылка ссылается на временный объект, исчезло, поэтому дальнейшее продление срока жизни невозможно.
Случай, который озадачивает вас
A const& refnop = A(a).nothing();
похож на этот случай:
A const& foo(A const& bar)
{
return bar;
}
//...
A const& broken = foo(A());
В обоих случаях временный привязывается к аргументу функции (неявный this
за nothing()
, bar
за foo()
) и получает свое время жизни "расширенным" до времени жизни аргумента функции. Я добавил "расширенный" в кавычки, потому что естественное время жизни временного элемента уже больше, поэтому фактическое продление не происходит.
Поскольку свойство продления времени жизни не является транзитивным, возвращение ссылки (которая ссылается на временный объект) не продлит срок жизни временного объекта, в результате чего оба refnop
а также broken
в конечном итоге ссылаясь на объекты, которые больше не существуют.
Мой оригинальный пример сложен.
Поэтому я публикую здесь более простой пример и приведу соответствующий абзац стандарта ISO C++.
Этот более простой пример также доступен на /questions/29175523/kak-nachat-izuchat-java-dlya-ispolzovaniya-s-oracle-rdbms/29175549#29175549
#include <iostream>
struct A
{
A(int i) { std::cout<<"Cstr "<< i<<'\n'; p = new int(i); }
~A() { std::cout<<"Dstr "<<*p<<'\n'; delete p; }
const A& thiz() const { return *this; }
int *p;
};
const A& constref( const A& a )
{
return a;
}
int main()
{
const A& a4 = A(4);
const A& a5 = A(5).thiz();
const A& a6 = constref( A(6) );
std::cout << "a4 = "<< *a4.p <<'\n';
std::cout << "a5 = "<< *a5.p <<'\n';
std::cout << "a6 = "<< *a6.p <<'\n';
}
Вывод с использованием командной строки g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
:
Cstr 4
Cstr 5
Dstr 5
Cstr 6
Dstr 6
a4 = 4
a5 = 0
a6 = 0
Dstr 4
Как вы можете видеть, временные объекты, на которые ссылаются a5
а также a6
разрушаются в конце функций thiz
а также constref
соответственно.
Это выдержка из §12.2 Временных объектов, где жирная часть применяется в этом случае:
Второй контекст, когда ссылка связана с временным. Временный объект, к которому привязана ссылка, или временный объект, являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется в течение всего времени существования ссылки, за исключением:
- Временная привязка к элементу ссылки в ctor-initializer конструктора (12.6.2) сохраняется до выхода из конструктора.
- Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.
- Время жизни временной привязки к возвращенному значению в операторе возврата функции (6.6.3) не продлевается; временное уничтожается в конце полного выражения в операторе возврата.
- Временная привязка к ссылке в новом инициализаторе (5.3.4) сохраняется до завершения полного выражения, содержащего новый инициализатор.
Это более полный пример:
#include <iostream>
struct A
{
A() { std::cout<<"Cstr 9\n"; p = new int(v = 9); }
A(int i) { std::cout<<"Cstr "<<i<<'\n'; p = new int(v = i); }
A(const A&o){ std::cout<<"Copy "<<o.v<<'\n'; p = new int(v = 10+o.v); }
~A() { std::cout<<"Del "<<v<<' '<<*p<<'\n'; *p = 88; delete p; }
const A& thiz() const { return *this; }
int *p;
int v;
};
const A& constref( const A& a )
{
return a;
}
std::ostream& operator<<( std::ostream& os, const A& a )
{
os <<"{ *p="<< *a.p <<" , v="<< a.v <<" }\n";
return os;
}
int main()
{
std::cout << "---const A a1 = A(1)" "\n";
const A a1 = A(1);
std::cout << "---const A a2 = A(2).thiz()" "\n";
const A a2 = A(2).thiz();
std::cout << "---const A a3 = constref( A(3) )" "\n";
const A a3 = constref( A(3) );
std::cout << "---const A& a4 = A(4)" "\n";
const A& a4 = A(4);
std::cout << "---const A& a5 = A(5).thiz()" "\n";
const A& a5 = A(5).thiz();
std::cout << "---const A& a6 = constref( A(6) )" "\n";
const A& a6 = constref( A(6) );
std::cout << "a1 = "<< a1;
std::cout << "a2 = "<< a2;
std::cout << "a3 = "<< a3;
std::cout << "a4 = "<< a4;
std::cout << "a5 = "<< a5;
std::cout << "a6 = "<< a6;
}
И соответствующий вывод, используя тот же g++
командная строка:
---const A a1 = A(1)
Cstr 1
---const A a2 = A(2).thiz()
Cstr 2
Copy 2
Del 2 2
---const A a3 = constref( A(3) )
Cstr 3
Copy 3
Del 3 3
---const A& a4 = A(4)
Cstr 4
---const A& a5 = A(5).thiz()
Cstr 5
Del 5 5
---const A& a6 = constref( A(6) )
Cstr 6
Del 6 6
a1 = { *p=1 , v=1 }
a2 = { *p=12 , v=12 }
a3 = { *p=13 , v=13 }
a4 = { *p=4 , v=4 }
a5 = { *p=0 , v=5 }
a6 = { *p=0 , v=6 }
Del 4 4
Del 13 13
Del 12 12
Del 1 1