В то время пока выходят статьи о сущности и подводных камнях r-value ссылок (пример со ссылками на полезные источники habrahabr.ru/post/157961) подозреваю, что довольно многие не знают особенности обычных l-value ссылок. Суть этой статьи показать пример, когда время жизни объекта определяется временем жизни l-value ссылки на него, и как это можно использовать. Если заинтересовало, то добро пожаловать. Кстати, зная как можно больше особенностей про l-value ссылки, будет проще понять r-value.
Можно считать, что все передают объекты по константной ссылке, когда это необходимо и довольно точно знают время жизни объекта.
Например:
В этом случае можно считать, что объект S() начнет разрушаться после вызова функции
А теперь самое интересное.
Но можно делать так:
Вывод (msvs 2012):
Но и это еще не все. Так же все знают, зачем нужен виртуальный деструктор, но давайте рассмотрим следующий пример, когда у базового класса деструктор не виртуальный. Продолжим использовать наш
Тут возникает вопрос: “А какая практическая польза?”. Один из ответов — это уже применяется в подходе
Ну как впечатления, а теперь вспомните про неявную конвертацию типов в случае, если конструктор не объявлен как explicit, выведение типов в шаблонных функциях и статью в начале поста.
Надеюсь, что кому-то эта статья открыла еще одну особенность С++.
Приложение и замечания.
Чтобы обезопасить тех, кто не полезет в стандарт и только начинающих С++ программистов, напоминаю, что в случае
В ходе написания статьи наткнулся на статью Х.Саттера http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
Плюс интересный пример http://www.rsdn.ru/forum/cpp/4257549.flat
Потому что в этом случае временный объект используется в выражении, которое явл��ется инициализатором, а тогда, как в случае с обычными функциями, время жизни временного объекта не распространяется дольше выражения.
Но на практике результат немного другой.
Вывод (msvs 2012):
Спасибо за внимание, удачного дня.
Можно считать, что все передают объекты по константной ссылке, когда это необходимо и довольно точно знают время жизни объекта.
Например:
struct S{}; void f(const S& value){} f(S());
В этом случае можно считать, что объект S() начнет разрушаться после вызова функции
f(). Почему довольно точно? — Потому что, в случае q(A(), B()); нe определён порядок создания и, соответственно, разрушения объектов A и B. Так же все знают, что нельзя писатьint& r = 1; // не компилируется
А теперь самое интересное.
Но можно делать так:
В этом случае согласно стандарту и Страуструпу (7.7.1)const int& r = 1;
- сначала применяется неявная конвертация к типу int
- затем значение складывается во временный объект типа int
- а теперь этот временный объект используется для инициализации нашей ссылки
struct Obj { Obj(int i) : m_i(i) { cout << "ctr: " << m_i << endl; } ~Obj() { cout << "dtr: " << m_i << endl; } Obj operator+(const Obj& value) { return Obj(m_i + value.m_i); } int m_i; }; ... Obj o1(1); const Obj& ro2 = Obj(2) + Obj(3); Obj o6(6);
- создастся объект o1
- создадутся
Obj(2)иObj(3)(последовательность стандартом не определяется) - создастся временный объект, которым проинициализируется
ro2 Obj(2)иObj(3)разрушатся- создастся
o6 - деструкторы будут вызваны в обратном порядке:
o5,временный объектиo1
Вывод (msvs 2012):
ctr: 1 ctr: 3 ctr: 2 ctr: 5 dtr: 2 dtr: 3 ctr: 6 dtr: 6 dtr: 5 dtr: 1
Но и это еще не все. Так же все знают, зачем нужен виртуальный деструктор, но давайте рассмотрим следующий пример, когда у базового класса деструктор не виртуальный. Продолжим использовать наш
Obj и добавимВывод:struct D : Obj { D(int i) : Obj(i) { cout << "D::ctr: " << m_i << endl; } ~D() { cout << "D::dtr: " << m_i << endl; } }; Obj o1(1); const Obj& ro2 = D(5); Obj o6(6);
Т.е. в этом случае, несмотря на то, что тип константной ссылкиctr: 1 ctr: 5 D::ctr: 5 ctr: 6 dtr: 6 D::dtr: 5 dtr: 5 dtr: 1
const Obj&, тем не менее, наш объект D “живет” пока “живет” ссылка на него.Тут возникает вопрос: “А какая практическая польза?”. Один из ответов — это уже применяется в подходе
ScopeGuard (http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758?pgno=2). Я лично не стал бы использовать такой подход и обернул бы нужный “хендл” ресурса в класс с соответствующим деструктором и конструктором.Ну как впечатления, а теперь вспомните про неявную конвертацию типов в случае, если конструктор не объявлен как explicit, выведение типов в шаблонных функциях и статью в начале поста.
Надеюсь, что кому-то эта статья открыла еще одну особенность С++.
Приложение и замечания.
Чтобы обезопасить тех, кто не полезет в стандарт и только начинающих С++ программистов, напоминаю, что в случае
временный объект разрушится перед выходом из функции, и вернувшаяся ссылка будет битой. Время жизни объекта определяется только локальной ссылкой. Лаконичнее и нагляднее стандарта сказать будет труднее, если интересно, то начните с параграфа 12.2. Вот цитата из стандарта (которая довольно часто встречает во всяких багзиллах и форумах):const Obj& f() {return Obj();}
The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits. A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.
В ходе написания статьи наткнулся на статью Х.Саттера http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
Плюс интересный пример http://www.rsdn.ru/forum/cpp/4257549.flat
Я бы предположил, что вывод должен быть#include <iostream> struct foo { ~foo() { std::cout << "~foo()\n"; } }; struct foo_holder { const foo &f; }; int main() { foo_holder holder = { foo() }; std::cout << "done!\n"; return 0; }
~foo() done!
Потому что в этом случае временный объект используется в выражении, которое явл��ется инициализатором, а тогда, как в случае с обычными функциями, время жизни временного объекта не распространяется дольше выражения.
Но на практике результат немного другой.
Вывод (msvs 2012):
И (g++ (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3):~foo() done! ~foo()
done! ~foo()
Спасибо за внимание, удачного дня.
