Есть ли правильный способ вернуть новый экземпляр объекта по ссылке в C ++? - PullRequest
13 голосов
/ 15 июня 2009

Итак, я писал некоторый код, и у меня было что-то вроде этого:

class Box
{
    private:
    float x, y, w, h;

    public:
    //...
    Rectangle & GetRect( void ) const
    {
        return Rectangle( x, y, w, h );
    }
};

Потом в каком-то коде:

Rectangle rect = theBox.GetRect();

Это работало в моей отладочной сборке, но в выпуске были "проблемы", возвращающие этот Rectangle по ссылке - я в основном получил неинициализированный прямоугольник. Класс Rectangle имеет оператор = и конструктор копирования. Не вдаваясь в причину этого, меня больше интересует правильный способ возврата (нового) объекта по ссылке с целью присвоения копирования в переменную. Я просто быть глупым? Разве это не должно быть сделано? Я знаю, что могу вернуть указатель и затем разыменование при назначении, но я бы предпочел не делать этого. Некоторая часть меня чувствует, что возвращение по значению приведет к избыточному копированию объекта - компилятор это выяснит и оптимизирует?

Это кажется тривиальным вопросом. Я чувствую себя почти смущенным, я не знаю этого после многих лет написания кода на C ++, так что, надеюсь, кто-то может прояснить это для меня. :)

Ответы [ 9 ]

24 голосов
/ 15 июня 2009

Вы не можете вернуть ссылку на временный объект в стеке. У вас есть три варианта:

  1. Вернуть его по значению
  2. Возврат по ссылке через указатель на то, что вы создали в куче с новым оператором.
  3. Возврат по ссылке, что вы получили по ссылке в качестве аргумента. [РЕДАКТИРОВАТЬ: Спасибо @ harshath.jr за указание на это]

Обратите внимание, что при возврате по значению, как в приведенном ниже коде, компилятор должен оптимизировать присваивание, чтобы избежать копирования - то есть он просто создаст один прямоугольник (прямоугольник), оптимизировав create + assign + copy в create. Это работает только при создании нового объекта при возврате из функции.

Rectangle GetRect( void ) const
{
    return Rectangle( x, y, w, h );
}

Rectangle rect = theBox.GetRect();
15 голосов
/ 15 июня 2009

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

7 голосов
/ 15 июня 2009

Возвращение объекта по значению (см. Пример ниже) на самом деле может быть дешевле, чем вы думаете. Компилятор часто оптимизирует лишнюю копию. Это называется оптимизация возвращаемого значения .

    Rectangle GetRect( void ) const
    {
            return Rectangle( x, y, w, h );
    }
3 голосов
/ 16 июня 2009

Есть ли верный способ вернуть новый экземпляр объекта по ссылке в C ++?

Нет, не по ссылке. Существует два способа создания нового объекта:

В стеке:

Rectangle makeRect()
{
  return Rectangle(x, y, w, h);
}
Rectangle r = makeRect(); // return by value

В куче:

Rectangle * makeRect()
{
  return new Rectangle(x, y, w, y);
}
Rectangle * r = makeRect(); // returned a pointer, don't forget to delete it later

Почему бы не что-то подобное?

class Box
{
  private:
    Rectangle mRectangle;

  public:
    Box(float x, float y, float w, float h) :
      mRectangle(x, y, w, h) // Forgive me for making assumptions
                             // about the inner workings of your
                             // code here.
    {
    }

    const Rectangle & GetRect() const
    {
      return mRectangle;
    }
};

Rectangle rect = theBox.GetRect();

«Назначение» должно работать сейчас. (Технически это не оператор присваивания, а вызываемый конструктор копирования.)

В надежде помочь

2 голосов
/ 15 июня 2009

Это невозможно. Ссылка - это еще одна форма указателя, и вы фактически возвращаете адрес объекта, который будет уничтожен (вызван деструктор) и, возможно, даже перезаписан к тому времени, когда вызывающая сторона получит контроль.

Вы можете либо

  • вызовите new и верните указатель (может быть, вам стоит подумать об умном указателе) на выделенный в куче объект или
  • возврат по значению или
  • передать объект по ссылке в функцию, чтобы он заполнил ее.
2 голосов
/ 15 июня 2009

Вы можете запутаться в концепции временного существования. Рассмотрим:

void f1( const A & a ) {
}

A f2() {
   return A;
}

f1( f2() );

Это код ОК, и стандарт говорит, что безымянный временный объект, создаваемый f2, должен висеть достаточно долго, чтобы его можно было использовать в f1.

Однако, ваш случай несколько другой. То, что возвращает ваша функция, является ссылкой, и, следовательно, безымянный временный объект также является ссылкой. Эта ссылка должна висеть достаточно долго, чтобы быть полезной, но вещь, к которой она относится, не обязательна.

2 голосов
/ 15 июня 2009
  • Либо верните ссылку на внутренности вашего класса Box (есть член Rectangle. Рекомендуется возвращать ссылку const).
  • или просто вернуть Rectangle. Обратите внимание, что использование идиомы return SomeClass(a,b,c); вероятно вызовет оптимизацию возвращаемого значения (RVO) на приличном компиляторе.

Проверьте детали реализации std::complex.

0 голосов
/ 15 июня 2009

мы можем использовать auto_ptr, если мы хотим использовать новый и безопасный от Memory Leak

class Box  { 

  private:    float x, y, w, h;   

  public:    

  //...    

  std::auto_ptr<Rectangle> GetRect( void ) const   
  {        
      return std::auto_ptr<Rectangle> ( new Rectangle( x, y, w, h ));   
  }

};
0 голосов
/ 15 июня 2009

Если поразрядный прямоугольник выглядит как Box, то есть состоит из четырех чисел с плавающей точкой (но получил различные функции-члены), вы можете использовать reinterpret_cast , хотя я бы вообще не рекомендовал его:

    const Rectangle & GetRect( void ) const
    {
            assert(sizeof(Rectangle) == sizeof(Box));
            return reinterpret_cast <Rectangle> (*this);
    }
...