LINUX.ORG.RU

[C++] ворзврат ссылки на локальный объект

 


0

4

Коллеги, объясните, как оно работает (если можно, со ссылками на документацию)

Почему функция

 
MyClass& func(const MyClass &rhs)
{
  Myclass tmp = rhs; // Конструктор копии определен корректно
  // преобразование tmp для заданных целей

  return tmp;
}
работает неверно (сегфолтится),

а функция

 
MyClass func(const MyClass &rhs)
{
  Myclass tmp = rhs; // Конструктор копии определен корректно
  // преобразование tmp для заданных целей

  return tmp;
}
работает корректно?

Правильно ли я понимаю, что return tmp вызывает конструктор копии, прежде, чем разрушить tmp по выходу из функции? Или это как-то по-другому работает? Или обе версии неправильные?

★★★★★

В первом случае ты возвращаешь ссылку на объект, который разрушается при выходе из метода, а значит доступа к нему уже нет. Во втором случае ты возвращаешь сам объект, так что все ок. Конструктор копирования тут, собственно, вообще не при чем.

jeyc
()
Ответ на: комментарий от jeyc

> Во втором случае ты возвращаешь сам объект, так что все ок.

Я не очень это понимаю: объект-то — tmp — временный, он ведь создан внутри функции. Я подозреваю, что «за сценой» компилятор может построить новый стековый объект по экземпляру tmp и именно этот стековый объект является результатом работы функции.

unanimous ★★★★★
() автор топика
Ответ на: комментарий от jeyc

> Конструктор копирования тут, собственно, вообще не при чем.

бгг

mannaz
()
Ответ на: комментарий от unanimous

Не уверен, что могу понять всю магию, но я прямо сейчас смотрю в дизассемблированный листинг и вызова конструктора копирования во время return точно не наблюдается.

jeyc
()
Ответ на: комментарий от jeyc

В стандарте вроде было написано, что в таких случаях вызов копирующего конструктора оставляется на усмотрение компилятора.

mannaz
()

>Или обе версии неправильные?

Верхняя версия неправильная. Нижнюю можно оптимизировать.

Absurd ★★★
()
Ответ на: комментарий от Pavval

> Происходит обычный Return value optimization.

правильно ли я понимаю, что в этом случае если объявить тип возвращаемого результата MyClass&, то объект tmp будет немедленно разрушен, а если тип результата MyClass, то он будет «жить» столько, сколько живет объект, стоящий в слева в вызове MyClass a = func(b)?

unanimous ★★★★★
() автор топика
Ответ на: комментарий от unanimous

Нет. Не правильно понимаешь. Все объекты созданные в стеке разрушаются при выходе из контекста. В первом случае ты копируешь объект переданный в функцию возвращаешь ссылку на копию и выходя из функции копию разрушаешь. Ссылка ссылается куда-то. Во втором случае ты копируешь объект переданный по ссылке и возвращаешь копию копии, а первую копию разрушаешь. Так понятно?

KblCb ★★★★★
()
Ответ на: комментарий от unanimous

правильно ли я понимаю, что в этом случае если объявить тип возвращаемого результата MyClass&, то объект tmp будет немедленно разрушен, а если тип результата MyClass, то он будет «жить» столько, сколько живет объект, стоящий в слева в вызове MyClass a = func(b)?

Не совсем понял вопроса (уставший я сейчас...), потому просто объясню подробнее:

MyClass& func(const MyClass &rhs)
{
  Myclass tmp = rhs; // создали объект в стеке (конструктор копирования)

  return tmp; // возвращаем на него ссылку
} // удаляем объект в стеке. Ссылка теперь указывает на несуществующий объект.

Дальше вариант работы без RVO:

MyClass func(const MyClass &rhs)
{
  Myclass tmp = rhs; // создали объект в стеке (конструктор копирования)

  return tmp; //конструктор копирования
} // удалили объект в стеке, ну и хрен с ним

При return value optimization фактически происходит следующее:

void func(const MyClass &rhs, Myclass& out)
{
  out = rhs; //MyClass::operator=()
  return;
}
Pavval ★★★★★
()
Ответ на: комментарий от mannaz

> В стандарте вроде было написано, что в таких случаях вызов копирующего конструктора оставляется на усмотрение компилятора.

Насколько я помню — только если возвращаемая ссылка константная. В любом случае такой код неочевиден, если подразумевается копирование, то и в интерфейсе должно быть явное копирование, возврат по значению.

Dendy ★★★★★
()

>Правильно ли я понимаю, что return tmp вызывает конструктор копии

Нет она вызывает копию конструктора.

Значит это чистый undefined behavior

undefined behavior будет если программист косой и делает в конструкторе копирования/удаления то чего там вообще быть не должно.

elverion
()

Вы тут все упоротые чтоли? Только Pavval и KblCb бред не несут.

anonymous
()
Ответ на: комментарий от jeyc

>Спасибо за справку. Значит это чистый undefined behavior.
Обожаю таких «специалистов», блджад. Сначала включи спеллчекер, а потом погугли что такое «undefined behaviour» в контексте С++

anonymous
()
Ответ на: комментарий от Miguel

> ссылки - это те же указатели, вид сбоку.

В общем случае это утверждение не верно, но как объяснение для школьника - покатит.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

>В общем случае это утверждение не верно, но как объяснение для школьника - покатит.

Плюсофаги такие плюсофаги. Десятилетиями по форумам одно и то же фуфло вещают с пафосом.

Absurd ★★★
()
Ответ на: комментарий от jeyc

> Не уверен, что могу понять всю магию, но я прямо сейчас смотрю в дизассемблированный листинг и вызова конструктора копирования во время return точно не наблюдается.

Возьми компилятор от мелкомятых. Он более точно следует стандарту

namezys ★★★★
()
Ответ на: комментарий от namezys

Возьми компилятор от мелкомятых. Он более точно следует стандарту

Возьми мозги и прочитай про RVO

Pavval ★★★★★
()
Ответ на: комментарий от Miguel

>> В общем случае это утверждение не верно

М-м-м... а чем, собственно?


При таком раскладе:

T& p = *static_cast<T*>(0);

компилятор имеет право послать на.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

>Ты, навреное, хотел что-то сказать? ;)

Начнем с того что ты ничего внятного не сказал, написал только чтото про «школьников», в духе типичного плюсанутого ЧСВ.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> В любом императивном языке кроме С++ ссылке можно присвоить null.

в С++ нет такой необходимости, можете считать это кривостью, костыльностью и т.д., точнее наверняка уже считаете, ваше дело, ваше право, только не стоит брызгать слюной - ваша позиция всем ясна

aho
()
Ответ на: комментарий от aho

>> В любом императивном языке кроме С++ ссылке можно присвоить null.

в С++ нет такой необходимости, можете считать это кривостью, костыльностью

Я такого не говорил и слюной не брызгал. Просто невозможность присвоить null - негодный критерий.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> В любом императивном языке кроме С++ ссылке можно присвоить null.

Там ссылка аналог указателю. Здесь ссылка - аналог объекта

namezys ★★★★
()
Ответ на: комментарий от LamerOk

При таком раскладе:

T& p = *static_cast<T*>(0);

компилятор имеет право послать на.

Точно?

Потому что сделать (даже случайно) ссылку, указывающую на 0, проблемы нет.

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

#include <iostream>

struct Test {
  Test() {std::cout << "Constructor" << std::endl;}
  ~Test() {std::cout << "Destructor" << std::endl;}
};

int main() {
  {
    const Test *a = &Test();
    std::cout << "---" << std::endl;
  }
  std::cout << "=========" << std::endl;
  {
    const Test &a = Test();
    std::cout << "---" << std::endl;
  }
  std::cout << "=========" << std::endl;
  return 0;
}
MigMit@migmit /cygdrive/e
$ g++ test.cpp -o test
test.cpp: In function 'int main()':
test.cpp:10: warning: taking address of temporary

MigMit@migmit /cygdrive/e
$ ./test
Constructor
Destructor
---
=========
Constructor
---
Destructor
=========

Miguel ★★★★★
()
Ответ на: комментарий от namezys

>Там ссылка аналог указателю. Здесь ссылка - аналог объекта

И то и то является синонимом объекта. Только «указатель» дает доступ к полям структуры через стрелочку, а «ссылка» - через точечку.

Absurd ★★★
()
Ответ на: комментарий от elverion

>То что в Java называется ссылка — в цпп называется указателем

Причем тут Java? В любом императивном языке ссылки работают как указатели на С++ за исключением адресной арифметики. В Python так же. В Лиспе так же.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> И то и то является синонимом объекта.

нет

разное поведение при выходе из области видимости

и указатель может быть null

namezys ★★★★
()
Ответ на: комментарий от namezys

>разное поведение при выходе из области видимости

Лучше однако не мяться фигней, а сразу использовать scoped_ptr или нечто подобное.

указатель может быть null

Это же хорошо?

Absurd ★★★
()
Ответ на: комментарий от namezys

>> Это же хорошо?

это прилестно

Ну возьми какой-нибудь Loki::SmartPtr с политикой RejectNullStatic. Он не сможет быть равным null. Делов то.

Absurd ★★★
()
Ответ на: комментарий от namezys

> А почему?

Потому что ссылка, инициализированная нулём, - это UB, и реализаторы некоторых компиляторов это отлавливают, хотя исключить этого нельзя. В стандарте сказано следующее:

A reference shall be initialized to refer to a valid object
or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only
way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer,
which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field.
—end note ]

LamerOk ★★★★★
()
Ответ на: комментарий от Miguel

>> T& p = *static_cast<T*>(0);

компилятор имеет право послать на.


Точно?


Да, см. выше.

Потому что сделать (даже случайно) ссылку, указывающую на 0, проблемы нет.


Да, естественно. Именно по этому я скзал «имеет право», а не «пошлет».


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


Это как раз логическое следствие из формальной недопустимости для ссылки NULL значения.

LamerOk ★★★★★
()
Ответ на: комментарий от Absurd

> Начнем с того что ты ничего внятного не сказал,

Какое слово из этой фразы:

В общем случае это утверждение не верно


тебе не понятно?

написал только чтото про «школьников», в духе типичного плюсанутого ЧСВ.


Пока что ЧСВ, уж не знаю, плюсанутое ли, типичное ли, только у тебя.

А для меня очевидно, что если человек не понимает судьбу созданного на стеке объекта, то выражение «T* const» он вряд ли распарсит, и парить ему мозги такими вещами нет никакого смысла.

LamerOk ★★★★★
()
Ответ на: комментарий от namezys

привидите пример когда7

У вас не работают клавиши «е» и "?"?

Собственно, не проблема. Вполне логичный код:

void libraryFunction(Class &arg) {
  ... // I wonder, what exactly would the "arg" point to?
}
void doSomething(Class *arg) {
  ...
  libraryFunction(*arg); //Perfectly normal, isn't it?
  ...
}
int main() {
  ...
  doSomething(null); //Really, we don't need ANYTHING there!
  ...
}

Miguel ★★★★★
()
Ответ на: комментарий от LamerOk

Да, см. выше.

Угу, спасибо.

Это как раз логическое следствие из формальной недопустимости для ссылки NULL значения.

Это ещё почему?

ИМХО, такое поведение больше похоже на грязный хак.

Miguel ★★★★★
()
Ответ на: комментарий от LamerOk

>А для меня очевидно, что если человек не понимает судьбу созданного на стеке объекта

При работе с С++ смысл выражения T* const это самое простое что можно вообще представить. Для того чтобы иметь дело с сообщениями об отсутствии подходящей перегрузки возникающими где-то в недрах STL плюсовую систему типов все-таки надо немного знать.

Absurd ★★★
()
Ответ на: комментарий от Absurd

> Он не сможет быть равным null.

Он не сможет быть null в runtime. Да, и оптимизация в этом случае может сплоховать и не выполнится.

namezys ★★★★
()
Ответ на: комментарий от LamerOk

> Потому что ссылка, инициализированная нулём, - это UB, и реализаторы некоторых компиляторов это отлавливают

Ну вообще да. Я бы руки оторвал. Но и может пропустить. Такой бывает.

namezys ★★★★
()
Ответ на: комментарий от Miguel

>> Это как раз логическое следствие из формальной недопустимости для ссылки NULL значения.

Это ещё почему?


Ссылка должна быть инициализирована при создании и валидна на протяжении всей свой жизни. Соответственно, выбросить временный объект, которым была инициализированна ссылка, по достижении sequence point нельзя - будет нарушено требование стандарта.

ИМХО, такое поведение больше похоже на грязный хак.


Совершенно верно. Это очень грязный хак, которого требует инвариант типа ссылка.

LamerOk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.