Как вернуть ссылки на объект, созданный внутри метода

Я рассуждаю о лучшем подходе к возврату ссылок на объекты, созданные внутри метода, как в следующей ситуации:

class A{
public:
    A(){}
    ~A(){}
};

class Foo{
public:
    Foo(){}
    ~Foo(){}
    A& create(int random_arg){
         // create object A and return its reference
    }
};

void other_method(){
     Foo f;
     A a = f.create();
     // do stuff with a
{

Я рассмотрел три возможных решения:

  1. создать необработанный указатель и вернуть ссылку, но это плохо, потому что нет гарантии, что объект будет правильно удален:

    A& create(int random_arg){
         A* a = new A();
         return *a;
    }
    
  2. создайте shared_ptr и верните shared_ptr по значению. Таким образом, shared_ptr позаботится об удалении объекта:

    shared_ptr<A> create(int random_arg){
         boost::shared_ptr<A> a_ptr(new A());
         return a_ptr;
    }
    
  3. создайте shared_ptr и верните ссылку:

    A& create(int random_arg){
         boost::shared_ptr<A> a_ptr(new A());
         return *a_ptr;
    }
    

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

Как вы думаете, как лучше всего поступить в этой ситуации? Есть ли другие возможности, которые я не рассмотрел?


person gcswoosh    schedule 12.03.2015    source источник
comment
почему ты хочешь сделать это?   -  person 0x499602D2    schedule 12.03.2015
comment
Почему бы просто не вернуть значение и позволить RVO побеспокоиться об этом?   -  person TartanLlama    schedule 12.03.2015
comment
Это может оказаться плохой идеей.   -  person Hayden    schedule 12.03.2015
comment
(1) приводит к утечкам памяти. (2) работает, но требует ненужных накладных расходов и сложности. (3) является неопределенным поведением, если кто-либо когда-либо просматривает возвращаемое значение. Вы не учли только A a; return a;. Следуйте принципу KISS.   -  person Jonathan Wakely    schedule 12.03.2015
comment
... Я должен добавить, что я имел в виду возврат по значению, не A& create() { A a; return a; } который имеет ту же проблему, что и (3)   -  person Jonathan Wakely    schedule 12.03.2015


Ответы (3)


Не делай этого. Скорее всего, у вас есть зависшая ссылка.

Вместо этого просто верните экземпляр A по значению.

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

person Bathsheba    schedule 12.03.2015
comment
Да, способ вернуть A — это вернуть A и избавиться от всех ненужных сложностей, которые приводят к утечкам памяти или неопределенному поведению. - person Jonathan Wakely; 12.03.2015
comment
Спасибо, это кажется самым простым и безопасным способом, но что, если A ссылается на другие объекты как на переменные-члены? Я не эксперт в конструкторе перемещения, означает ли это, что я должен реализовать глубокую копию объекта внутри конструктора перемещения, чтобы копировать также объекты, на которые ссылается A? - person gcswoosh; 12.03.2015
comment
Не рекомендуется иметь ссылки в качестве переменных-членов, поскольку вам всегда придется держать объекты, на которые ссылаются, в области видимости на протяжении всего срока службы вашего экземпляра. Я бы посоветовал (i) удалить все такие элементы и (ii) разрешить компилятору опускать копии объектов, где это возможно. Позаботьтесь об оптимизации на более позднем этапе. Кроме того, взгляните на std::shared_ptr: это может быть полезно. - person Bathsheba; 12.03.2015
comment
Спасибо, так что вы предлагаете лучше инициализировать члены класса, передавая конструктору другие объекты по значению, а не по ссылке, я прав? - person gcswoosh; 12.03.2015
comment
Нет, передавать переменные по константной ссылке на конструктор, но не использовать ссылочные типы для переменных-членов. - person Bathsheba; 12.03.2015

Чтобы завершить ответ Вирсавии:

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

A& getTheOnlyInstanceOfA()
{
   static A onlyInstOfA;
   return onlyInstOfA; 
}
person sbabbi    schedule 12.03.2015

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

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

Возврат по значению может быть применим во многих случаях, компилятор может оптимизировать возврат. Но даже без оптимизации это может не иметь большого значения, например, в Qt все контейнеры и многие другие классы неявно распределяют ресурсы, поэтому при возврате путем копирования на самом деле копируются не все данные, а только оболочка объекта, обычно один указатель на фактические данные. В C++ также есть семантика перемещения, которая в этом отношении похожа — она переносит ответственность с локального на возвращаемый объект без глубокого копирования.

Есть два основных случая, когда возврат по значению нежелателен.

  • если речь идет о неизбежной копии тяжелого объекта
  • если это объект с «идентификацией» - у них отключен конструктор копирования и операторы присваивания, поэтому они не могут быть возвращены по значению

В этих случаях вы просто динамически выделяете объект и возвращаете указатель на него. Управляете ли вы им вручную или предпочитаете использовать интеллектуальный указатель — это зависит от ваших потребностей.

person dtech    schedule 12.03.2015