Мне нужно забрать владение над TrackedClass у Wrapper.
И вот в чем прикол. В онлайн-компиляторе вывод:
Constructor id=1
Destructor id=1
desctructed=false
test
А у меня в XCode тот же самый код:
Constructor id=1
Destructor id=1
desctructed=true
test
Т.е. если так попытаться заиметь владение — можно получить по башке, получается?
Получается в онлайн этом компилере — деструктор то вызывается, но видимо он сбрасывает значение поля? Т.е. в любом случае такое перемещение — не позволяет сохранить объект и передать.
Добавил:
TrackedClass&& fun1() {
Wrapper w = Wrapper();
return std::move(w.take());
}
Здравствуйте, Shmj, Вы писали:
S>Т.е. если так попытаться заиметь владение — можно получить по башке, получается?
Не можно, а нужно. Время жизни объекта закончилось при выходе из функции. И ссылка, которую ты вернул, оказывается невалидной, прямо сразу же. Всё дальнейшее поведение программы — это UB.
Сколько лет ты уже толчёшься на этом форуме, но не потрудился освоить даже азы. Какие слова тебе нужно сказать, чтоб до тебя дошло. что C++ — не C#?
--
Справедливость выше закона. А человечность выше справедливости.
Вот это очень наивная попытка обнаружить окончание времени жизни объекта. Если объект уже разрушен, то вызов нестатической функции-члена для этого объекта порождает неопределённое поведение. Вместо статуса объекта программа может вывести тебе расписание поездов на Луну. А может отформатировать винчестер.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, T4r4sB, Вы писали:
TB>Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении
Ну смотрите, получается что обычный возврат return из функции — может порождать копию. Так ведь?
class Wrapper {
private:
TrackedClass _trackedClass;
public:
Wrapper() : _trackedClass(1) {
}
TrackedClass take() {
return _trackedClass; // <- тут копия порождается.
}
};
TrackedClass fun1() {
Wrapper w = Wrapper();
TrackedClass t = w.take();
return t;
}
Однако если срабатывает некая оптимизация, то копия уже не порождается. К примеру тут:
class Wrapper {
private:
TrackedClass _trackedClass;
public:
Wrapper() : _trackedClass(1) {
}
TrackedClass take() {
TrackedClass tc = std::move(_trackedClass); // <- Копия не порождается.return tc;
}
};
TrackedClass fun1() {
Wrapper w = Wrapper();
TrackedClass t = w.take(); // <- копия НЕ порождается.return t;
}
int main(int argc, const char * argv[]) {
TrackedClass t = fun1();
t.test();
std::cout << "test" << std::endl;
}
Правильно ли я понял, что это благодаря умному компилятору и NRVO ? И всегда ли можно на это рассчитывать, всегда ли имеем 100% гарантию что копия не порождается?
Здравствуйте, rg45, Вы писали:
R>Сколько лет ты уже толчёшься на этом форуме, но не потрудился освоить даже азы. Какие слова тебе нужно сказать, чтоб до тебя дошло. что C++ — не C#?
Так дело вот в чем. Я с 15 декабря примерно без выходных и праздников работаю с другим языком (не прикасался к C++). Мозг работает на 30%.
И тут срочно понадобилось внести правки в старый C++ проект.
И вроде бы разбирался с этим всем — но сейчас другой язык с умным сборщиком мусора очистил и облегчил мой мозг — и приходится как бы заново разбираться.
Здравствуйте, Shmj, Вы писали:
S>Так дело вот в чем. Я с 15 декабря примерно без выходных и праздников работаю с другим языком (не прикасался к C++). Мозг работает на 30%.
Смотри какая штука. В С++ никогда нельзя выпускать из виду время жизни объектов. Нет и быть не может универсального ответа на вопрос, как правильно возвращать — по ссылке или по значению. Всё определяется решаемой задачей и семантикой того или иного класса и той или иной функции. Это же относится и к твоему классу враппера. Его можно было бы реализовать и так, и эдак, вероятно, какое-то решение оказалось бы более выгодным. Чтобы выбрать оптимальный вариант дизайна, нужно знать больше деталей, которые не видны в твоём синтетическом примере. Но вот при реализации функции fun1, программист однозначно прошёлкал этот момент. Каким бы ни был дизай класса враппера, как бы он ни возвращал результат, по ссылке или по значению, функция fun1 реализована некорректно и возвращает битую ссылку. И чтобы исправить ошибку, нужно просто сделать, чтоб fun1 возвращала по значению.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, T4r4sB, Вы писали:
TB>>Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении
S>Ну смотрите, получается что обычный возврат return из функции — может порождать копию. Так ведь?
S>Правильно ли я понял, что это благодаря умному компилятору и NRVO ? И всегда ли можно на это рассчитывать, всегда ли имеем 100% гарантию что копия не порождается?
Есть правила когда оптимизация точно сработает.
Есть случаи когда она точно НЕ сработает, и как минимум надо понимать как эта оптимизация устроена технически. Например при возврате поля компилятор не знает нужно ли тебе это поле в дальнейшем, так что если нет то руками ставь std move
И есть случаи когда зависит от компилятора но их сравнительно мало.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, rg45, Вы писали:
R>Каким бы ни был дизай класса враппера, как бы он ни возвращал результат, по ссылке или по значению, функция fun1 реализована некорректно и возвращает битую ссылку. И чтобы исправить ошибку, нужно просто сделать, чтоб fun1 возвращала по значению.
Здесь сначала происходит перемещение содержимого объекта _trackedClass в локальный объект tc. И нужно понимать, что время жизни объекта _trackedClass при этом не заканчивается. Скорлупа этого (под)объекта будет жить, пока живет его полный объект. Также примечательно то, что объект tc является move eligible. Это означает, что, если к нему не будет применена NRVO, то к нему будет применено ещё одно перемещение. Т.е. либо NRVO (что скорее всего), либо второе перемещение, копирования точно не будет.
Как бы то ни было, хотя бы одного перемещения здесь не избежать. Поэтому смысла в этом локальном объекте tc нет никакого. Лучше писать просто:
Так и код проще, и потенциальное число перемещений меньше.
S>
S>TrackedClass fun1() {
S> Wrapper w = Wrapper();
S> TrackedClass t = w.take(); // <- копия НЕ порождается.
S> return t;
S>}
S>
Строго говоря, здесь копия может порождаться, а может нет. Стандарт языка не регламентирует число промежуточных копий, это implementation specifics. Но, если NRVO таки было подключено, что скорее всего, тогда никаких дополнительных копий не будет.
К счастью, этот пример можно легко видоизменить так, чтоб вместо необязательной NRVO применялась обязательная RVO:
TrackedClass fun1() {
Wrapper w = Wrapper();
return w.take(); // <- копия НЕ порождается СТОПУДОВО!
}
То, что в этом случает не будет промежуточных копий, гарантируется стандартом языка.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Как бы то ни было, хотя бы одного перемещения здесь не избежать. Поэтому смысла в этом локальном объекте tc нет никакого.
А раз уж хотя бы одного перемещения не избежать ни при каких раскладах, твоя функция-член take може спокойненько возвращать rvalue ссылку. И перемешение в этом случае будет происходить не в take, а в fun1:
TrackedClass&& take() {
return std::move(_trackedClass); // Ни копии, ни перемещения - просто возврат rvalue ссылки.
}
TrackedClass fun1() {
Wrapper w = Wrapper();
return w.take(); // То самое одно неизбежное перемещение.
}
Но признаться, меня несколько напрягает такой дизайн. Вот нафига вот это безусловное встраивание move внутрь каждого неконстантного объекта? Не лучше ли просто обеспечить оптимальный доступ к члену класса в зависимости от категории выражения, а вызвающий метод уже пускай сам делает move, если захочет:
Здравствуйте, Marty, Вы писали:
S>>Во втором примере — все работает как нужно, копирования не происходит. Но есть ли гарантия что это будет работать везде? M>Ну что за чушь? Если UB у тебя случайно работает, как ты ожидаешь, то тебе просто очень повезло. А могло бы и винт отформатировать
А где вы там увидели UB, тем более уровня критического. Максимум что там может произойти — излишнее копирование.