Comments 12
В этом случае можно считать, что объект S() начнет разрушаться после вызова функции f()
Звучит как в буддизме: «мы начинаем умирать сразу после рождения» :) Ну т.е. я имею ввиду, что, имхо, это предложение нужно перефразировать.
Я недавно этот фокус использовал для удобного конвертирования между различными кодировками строк — UTF8/UTF16/UTF32, когда вынужденно портировал большой кусок кода на Linux, при том, что еще использовалась библиотека нуждающаяся в UTF16 аргументах (сам wchar_t в Linux — 4х байтный). Сделал себе классы-врапперы «U8», «U16», и «U32», скрывающие всю грязную работу, и все конвертирование меж кодировками в самом коде выглядило примерно так:
functionCall ( U8( lpSomeUTF32String ).get() );
Где соответственно метод .get() отдавал уже c-string обычный. Проверял, такая например лесенка:
functionCall ( U8( U8( U8( U8( U8( arg ).get() ).get() ).get() ).get() ).get() );
Прекрасно живет, и вся эта пачка «вложенных» объектов сначала строго последовательно создается, и потом строго последовательно удаляется, после выхода из «functionCall».
functionCall ( U8( lpSomeUTF32String ).get() );
Где соответственно метод .get() отдавал уже c-string обычный. Проверял, такая например лесенка:
functionCall ( U8( U8( U8( U8( U8( arg ).get() ).get() ).get() ).get() ).get() );
Прекрасно живет, и вся эта пачка «вложенных» объектов сначала строго последовательно создается, и потом строго последовательно удаляется, после выхода из «functionCall».
Почему вывод у msvc2012 такой? т.е. два раза вызван деструктор в последнем примере?
Создаётся первый объект класса foo конструктором без аргументов, создаётся временный объект копирующим конструктором, первый объект уничтожается, заканчивается main(), временный объект уничтожается. Если добавить явный конструктор (пусть даже пустой)
то вывод будет как и у gcc (видимо, срабатывает оптимизация RVO/NRVO?)
#include <iostream>
struct foo {
foo() {}
~foo() {
std::cout << "~foo()\n";
}
};
struct foo_holder {
const foo &f;
};
int main() {
foo_holder holder = { foo() };
std::cout << "done!\n";
return 0;
}
то вывод будет как и у gcc (видимо, срабатывает оптимизация RVO/NRVO?)
int& r = 1; // не компилируется
test.cpp:3:11: ошибка: invalid initialization of non-const reference of type «int&» from an rvalue of type «int»
компилятор как бы намекает почему нельзя. Из чего сразу понятно почему
const int& r = 1;
можно
Когда время жизни объекта определяется временем жизни ссылки на него
Я бы перефразировал
Вау, можно создавать константный ссылки на rvalue объекты (и это работает если у них одна область видимости)
Вводит в небольшой ступор последний пример. Мне кажется что это из за того, что синтаксис { }
foo_holder holder = { foo() };
это именно синтаксис и он не связан с ограничением области видимости как в ( ) и что объект { foo() }; имеет туже области видимости что и holder.
осознал никчемность и неправоту своего комментария :( ссылка действительно продлевает время жизни объекта.
осознал никчемность и неправоту своего комментария :( ссылка действительно продлевает время жизни объекта.
#include <iostream>
class Temp
{
public:
Temp(int i):i_(i){std::cout << "Temp(" << i_ << ")\n";}
~Temp(){std::cout << "~Temp(" << i_ << ")\n";}
private:
int i_;
};
int main()
{
std::cout << "Start main\n";
Temp(1);
const Temp& ref = Temp(2);
std::cout << "Finish main\n";
}
Start main
Temp(1)
~Temp(1)
Temp(2)
Finish main
~Temp(2)
Опечатка после первого немаленького листига:
…
создастся o5
деструкторы будут вызваны в обратном порядке: o5, временный объект и o1
…
Вместо o5 должно быть o6.
…
создастся o5
деструкторы будут вызваны в обратном порядке: o5, временный объект и o1
…
Вместо o5 должно быть o6.
Несколько лет назад в нашу компанию как-то тоже -то откуда-то пришла лихорадка к «константным ссылкам, продлевающим жизнь объекта». Вместо привычного сердцу программиста кода:
стало принято писать:
Сам грешил этим и оправдывал сие действие оптимизацией кода, «чтобы не было лишнего копирования». Сразу возвражу этому аргументу возможностью современных оптимизаторов уметь конструировать результат сразу в фрейме вызывающей функции, так что эта оптимизация сомнительна. С другой стороны данная нотация добавляет нечитабельности коду и, что еще хуже, дает возможность жестоко покрашиться:
Баг заключается в том, что в коде const Computing& result = Zero().Plus(1); лексически на временный объект, возвращаемый из Zero() нет ни одной константной ссылки, и то, что эта ссылка возвращается из Plus, всего лишь фактическое положение вещей и компилятором учитываться не может. Поэтому временный объект уничтожится, как ему и полагается, по достижении точки с запятой и константная ссылка будет ссылаться на мусор.
Заключение: используйте константные ссылки только в качестве аргументов, и ваш код будет надежнее и читабельнее.
SomeClass object = SomeFunction();
стало принято писать:
const SomeClass& object = SomeFunction();
Сам грешил этим и оправдывал сие действие оптимизацией кода, «чтобы не было лишнего копирования». Сразу возвражу этому аргументу возможностью современных оптимизаторов уметь конструировать результат сразу в фрейме вызывающей функции, так что эта оптимизация сомнительна. С другой стороны данная нотация добавляет нечитабельности коду и, что еще хуже, дает возможность жестоко покрашиться:
struct Computing {
Computing() : Current() {}
Computing& Plus(int argument) {
Current += argument;
return *this;
}
int GetResult() const {
return Current;
}
private:
int Current;
};
Computing Zero() {
return Computing();
}
int main() {
const Computing& result = Zero().Plus(1); // ВНИМАНИЕ: Баг!
std::cout << result.GetResult(); // Неопределенное поведение
}
Баг заключается в том, что в коде const Computing& result = Zero().Plus(1); лексически на временный объект, возвращаемый из Zero() нет ни одной константной ссылки, и то, что эта ссылка возвращается из Plus, всего лишь фактическое положение вещей и компилятором учитываться не может. Поэтому временный объект уничтожится, как ему и полагается, по достижении точки с запятой и константная ссылка будет ссылаться на мусор.
Заключение: используйте константные ссылки только в качестве аргументов, и ваш код будет надежнее и читабельнее.
Решил проверить, и да, все верно вы сказали. для тех кто сомневается, подробный пример:
#include <iostream>
struct Computing {
Computing() :
Current() {
std::cout << "build Computing\n";
}
~Computing() {
Current = 0;
std::cout << "destroy Computing\n";
}
Computing& Plus(int argument) {
std::cout << "plus argument\n";
Current += argument;
return *this;
}
int GetResult() const {
std::cout << "getting result\n";
return Current;
}
Computing(const Computing& other) {
std::cout << "copy constructor Computing\n";
Current = other.Current;
}
private:
int Current;
};
Computing Zero() {
return Computing();
}
int main() {
std::cout << "start correct code\n";
{
Computing result = Zero().Plus(1);
std::cout << "result is " << result.GetResult() << "\n";
}
std::cout << "finish correct code\n";
std::cout << "\nstart incorrect code\n";
{
const Computing& result = Zero().Plus(1); // ВНИМАНИЕ: Баг!
std::cout << "result is " << result.GetResult() << "\n"; // Неопределенное поведение
}
std::cout << "finish incorrect code\n";
}
Полностью поддерживаю, такой подход не для продакшена, покрашиться очень легко. Именно поэтому топик в хабе «ненормальное программирование».
Основная идея поста — показать неочевидную особенность ссылок, чтобы предотвратить подобные ошибки и лучше различать r-value и l-value ссылки.
Основная идея поста — показать неочевидную особенность ссылок, чтобы предотвратить подобные ошибки и лучше различать r-value и l-value ссылки.
Sign up to leave a comment.
C++: Когда время жизни объекта определяется временем жизни ссылки на него