константная ссылка на временный объект становится неработоспособной после области действия функции (время жизни)

Спрашивая этот вопрос, Я узнал, что постоянная ссылка на временный объект действительна в 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время жизни временного объекта не совпадает с его постоянной ссылкой?

5

Решение

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

Случай, который озадачивает вас

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 в конечном итоге ссылаясь на объекты, которые больше не существуют.

10

Другие решения

Мой оригинальный пример сложен.

Поэтому я публикую здесь более простой пример и приведу соответствующий Стандарт ISO C ++ параграф.

Этот более простой пример также доступен на coliru.stacked-crooked.com/

#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
1