Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

C : Когда время жизни объекта определяется временем жизни ссылки на него

Anna | 25.06.2014 | нет комментариев
В то время пока выходят статьи о сущности и подводных камнях r-value ссылок (пример со ссылками на пригодные источники habrahabr.ru/post/157961/) подозреваю, что достаточно многие не знают особенности обыкновенных l-value ссылок. Суть этой статьи показать пример, когда время жизни объекта определяется временем жизни l-value ссылки на него, и как это дозволено применять. Если заинтересовало, то благо пожаловать. Кстати, зная как дозволено огромнее особенностей про l-value ссылки, будет проще осознать r-value.

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

struct S{};
void f(const S& value){} 
f(S());

В этом случае дозволено считать, что объект S() начнет разрушаться позже вызова функции f(). Отчего достаточно верно? — Потому что, в случае q(A(), B()); нe определён порядок создания и, соответственно, уничтожения объектов A и B. Так же все знают, что невозможно писать

int& r = 1; // не компилируется

А сейчас самое увлекательное.
Но дозволено делать так:

const int& r = 1;

В этом случае согласно эталону и Страуструпу (7.7.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) разрушатся
  • создастся o5
  • деструкторы будут вызваны в обратном порядке: 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, выведение типов в шаблонных функциях и статью в начале поста.

Верю, что кому-то эта статья открыла еще одну специфика С .

Приложение и примечания.

Дабы обезопасить тех, кто не полезет в эталон и только начинающих С программистов, напоминаю, что в случае

const Obj& f() {return Obj();}

непостоянный объект разрушится перед выходом из функции, и вернувшаяся ссылка будет битой. Время жизни объекта определяется только локальной ссылкой. Лаконичнее и нагляднее эталона сказать будет сложнее, если увлекательно, то начните с параграфа 12.2. Вот цитата из эталона (которая достаточно Зачастую встречает во любых багзиллах и форумах):

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):

~foo()
done!
~foo()

И (g (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3):

done!
~foo()

Спасибо за внимание, благополучного дня.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB