Является ли практика возврата ссылочной переменной C ++ злом?

Я думаю, это немного субъективно; Не уверен, что мнение будет единодушным (я видел много фрагментов кода, в которых возвращаются ссылки).

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

Это меня беспокоит, поскольку я следовал примерам (если только я не воображал что-то) и делал это в нескольких местах ... Я неправильно понял? Это зло? Если так, то насколько зло?

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

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


person Community    schedule 15.04.2009    source источник
comment
Это не зло, если вы пишете функции / методы, подобные геттеру.   -  person John Z. Li    schedule 10.02.2018


Ответы (16)


В общем, возврат ссылки - это нормально и происходит постоянно.

Если ты имеешь ввиду:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

Это все виды зла. Выделенный стеком i исчезнет, ​​и вы ни на что не ссылаетесь. Это тоже зло:

int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

Потому что теперь клиенту в конечном итоге приходится делать странное:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil

int oops = getInt(); 
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

Обратите внимание, что ссылки rvalue по-прежнему являются просто ссылками, поэтому все вредоносные приложения остаются прежними.

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

std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

И теперь клиент хранит умный указатель:

std::unique_ptr<int> x = getInt();

Ссылки также подходят для доступа к вещам, время жизни которых, как вы знаете, остается открытым на более высоком уровне, например:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

Здесь мы знаем, что можно возвращать ссылку на i_, потому что все, что нас вызывает, управляет временем жизни экземпляра класса, поэтому i_ будет жить как минимум так долго.

И, конечно же, нет ничего плохого в том, чтобы просто:

int getInt() {
   return 0;
}

Если время жизни следует оставить на усмотрение вызывающей стороны, а вы просто вычисляете значение.

Резюме: можно вернуть ссылку, если время жизни объекта не закончится после вызова.

person Community    schedule 15.04.2009
comment
Все это плохие примеры. Лучший пример правильного использования - это когда вы возвращаете ссылку на переданный объект. Оператор ala ‹ - person Arelius; 11.06.2011
comment
Для потомков и для начинающих программистов, которые сталкиваются с этим, указатели - это неплохо. Указатели на динамическую память тоже не плохи. У них обоих есть свои законные места в C ++. Умные указатели определенно должны быть вашим выбором по умолчанию, когда дело доходит до управления динамической памятью, но ваш умный указатель по умолчанию должен быть unique_ptr, а не shared_ptr. - person Jamin Grey; 07.03.2013
comment
Единственный раз, когда я считаю возврат по ссылке полезным, - это при связывании вызовов функций. Пример: Class& Class::translate(...) { ...; return *this}; Class& Class::rotate(...) { ...; return *this;}; Class obj; obj.translate(...).rotate(...).translate(...);. - person Jayesh; 06.12.2013
comment
Утверждающие редактирование: не утверждайте правки, если вы не можете поручиться за их правильность. Я откатил неправильную правку. - person GManNickG; 20.03.2014
comment
Ради потомков и для начинающих программистов, которые сталкиваются с этим, не пишите return new int. - person Lightness Races in Orbit; 09.04.2015
comment
Ради потомков и для начинающих программистов, которые сталкиваются с этим, просто верните T из функции. RVO обо всем позаботится. - person Shoe; 09.04.2015
comment
int badInt = * getInt (); удалить // не сработает. badInt был копией выделенного int, который // теперь утерян навсегда - person Kaiserludi; 21.05.2015
comment
Второй случай int& getInt(void) { int *i = new int; return *i; } - самое злое, что я когда-либо видел - person UmNyobe; 22.04.2016
comment
@ Андрей Где ты картошку продаешь? : D - person Klaider; 11.05.2017
comment
Разве пример immutableint не опасен, потому что вызывающий может удерживать int& даже после уничтожения immutableint? Этот шаблон рекомендуется / не рекомендуется в общих руководствах по стилю? - person bluenote10; 20.08.2018
comment
@ bluenote10: Это не немедленно опасно, как другие, но открывает возможность использования после освобождения. Однако это проблема со ссылками в целом, не связанными с функциями. Обычно гораздо труднее иметь ссылку пережить то, на что она ссылается таким образом. Это довольно распространено: например, operator[] почти всегда возвращает ссылку (или прокси, содержащий ссылку), как в std::vector. - person GManNickG; 20.08.2018

Нет, нет, тысячу раз нет.

Зло - это ссылка на динамически выделяемый объект и потеря исходного указателя. Когда вы new объект, вы берете на себя обязательство иметь гарантированный delete.

Но взгляните, например, на operator<<: он должен возвращать ссылку, или

cout << "foo" << "bar" << "bletch" << endl ;

не сработает.

person Community    schedule 15.04.2009
comment
Я проголосовал против, потому что это не отвечает на вопрос (в котором OP ясно дал понять, что он знает о необходимости удаления) и не устраняет законные опасения, что возврат ссылки на объект freestore может привести к путанице. Вздох. - person ; 15.04.2009
comment
Практика возврата ссылочного объекта не зла. Ergo no. Страх, который он выражает, - правильный страх, как я указываю во втором графике. - person Charlie Martin; 15.04.2009
comment
На самом деле вы этого не сделали. Но это того не стоит. - person ; 15.04.2009
comment
Iraimbilanja @ Нет, мне все равно. но этот пост указал на важную информацию, которой не хватало в GMan. - person Kobor42; 26.05.2013

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

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

Вы можете вернуть ссылку на что-то независимое от функции, и вы не ожидаете, что вызывающая функция возьмет на себя ответственность за удаление. Это случай типичной функции operator[].

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

person Community    schedule 15.04.2009
comment
Отличный ответ, но вы можете вернуть временную как ссылку на константу. Следующий код будет компилироваться, но, вероятно, выйдет из строя, потому что временное будет уничтожено в конце оператора return: int const & f () {return 42; } void main () {int const & r = f (); ++ r; } - person j_random_hacker; 16.04.2009
comment
@j_random_hacker: C ++ имеет некоторые странные правила для ссылок на временные библиотеки, временное время жизни которых может быть увеличено. Извините, я недостаточно хорошо его понимаю, чтобы знать, относится ли оно к вашему случаю. - person Mark Ransom; 16.04.2009
comment
@Mark: Да, у него есть какие-то странные правила. Время жизни временного объекта можно продлить только путем инициализации с его помощью ссылки на константу (которая не является членом класса); Затем он живет до тех пор, пока ссылка не выйдет из области видимости. К сожалению, возврат const ref не покрыт. Однако возврат временного значения по значению безопасен. - person j_random_hacker; 17.04.2009
comment
См. Стандарт C ++, 12.2, параграф 5. Также см. «Бродячий гуру недели» Херба Саттера по адресу grassutter.wordpress.com/2008/01/01/. - person David Thornley; 17.04.2009
comment
@David: Сначала я тоже так это интерпретировал, но на самом деле этот абзац и пример Саттера не применяются к функции, возвращаемый тип которой T const & - функция должна возвращать T по значению , который затем используется для инициализации переменной типа T const & для продления срока службы. - person j_random_hacker; 18.04.2009
comment
@David: когда типом возвращаемого значения функции является T const &, на самом деле происходит то, что оператор return неявно преобразует значение temp типа T в тип T const & согласно 6.6.3.2 (законный преобразование, но такое, которое не продлевает время жизни), а затем вызывающий код инициализирует ссылку типа T const & результатом функции, также типа T const & - опять же, законный, но не продлевающий время жизни процесс. Конечный результат: не продление срока службы и много путаницы. :( - person j_random_hacker; 18.04.2009

Я считаю, что ответы неудовлетворительны, поэтому я добавлю свои два цента.

Разберем следующие случаи:

Ошибочное использование

int& getInt()
{
    int x = 4;
    return x;
}

Это очевидно ошибка

int& x = getInt(); // will refer to garbage

Использование со статическими переменными

int& getInt()
{
   static int x = 4;
   return x;
}

Это правильно, потому что статические переменные существуют на протяжении всего времени жизни программы.

int& x = getInt(); // valid reference, x = 4

Это также довольно часто при реализации паттерна Singleton.

Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };

        void printHello()
        {
             printf("Hello");
        };

}

Использование:

 Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
 my_sing.printHello();  // "Hello"

Операторы

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

T & operator*();

может использоваться в следующих

std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now

Быстрый доступ к внутренним данным

Бывают случаи, когда & может использоваться для быстрого доступа к внутренним данным

Class Container
{
    private:
        std::vector<int> m_data;

    public:
        std::vector<int>& data()
        {
             return m_data;
        }
}

с использованием:

Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

ОДНАКО это может привести к такой ловушке:

Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!
person Community    schedule 22.04.2016
comment
Возврат ссылки на статическую переменную может привести к нежелательному поведению, например. рассмотрим оператор умножения, который возвращает ссылку на статический член, тогда в результате всегда будет true: If((a*b) == (c*d)) - person SebNag; 21.12.2016
comment
Реализация Container::data() должна читать return m_data; - person Xeaz; 21.02.2017
comment
Это было очень полезно, спасибо! @Xeaz, разве это не вызовет проблемы с вызовом добавления? - person Andrew; 26.02.2017
comment
@ Андрей Нет, это синтаксическая махинация. Если вы, например, вернули тип указателя, то вы должны использовать адрес ссылки для создания и возврата указателя. - person thorhunter; 27.02.2017

Это не зло. Как и многие вещи в C ++, он хорош, если используется правильно, но есть много подводных камней, о которых вы должны знать при его использовании (например, возврат ссылки на локальную переменную).

С его помощью можно достичь хороших результатов (например, map [name] = "hello world")

person Community    schedule 15.04.2009
comment
Мне просто любопытно, чем хорош map[name] = "hello world"? - person wrongusername; 20.10.2011
comment
@wrongusername Синтаксис интуитивно понятен. Вы когда-нибудь пытались увеличить счетчик значения, хранящегося в HashMap<String,Integer> в Java? :П - person mmx; 20.10.2011
comment
Ха-ха, пока нет, но глядя на примеры HashMap, это действительно выглядит довольно коряво: D - person wrongusername; 20.10.2011
comment
У меня возникла проблема: функция возвращает ссылку на объект в контейнере, но код вызывающей функции назначил ее локальной переменной. Затем изменили некоторые свойства объекта. Проблема: исходный объект в контейнере остался нетронутым. Программист так легко упускает из виду символ & в возвращаемом значении, и тогда вы получаете действительно неожиданное поведение ... - person flohack; 10.06.2014

"возвращать ссылку - зло, потому что просто [как я понимаю] это упрощает ее удаление"

Не правда. Возврат ссылки не подразумевает семантику владения. То есть просто потому, что вы делаете это:

Value& v = thing->getTheValue();

... не означает, что теперь вы владеете памятью, на которую указывает v;

Однако это ужасный код:

int& getTheValue()
{
   return *new int;
}

Если вы делаете что-то подобное, потому что «вам не нужен указатель в этом экземпляре ", тогда: 1) просто разыменуйте указатель, если вам нужна ссылка, и 2) вам в конечном итоге понадобится указатель, потому что вам нужно сопоставить новое с удалением, и вам нужен указатель для вызова удалять.

person Community    schedule 15.04.2009

Есть два случая:

  • const reference - хорошая идея, иногда, особенно для тяжелых объектов или прокси-классов, оптимизация компилятора

  • неконстантная ссылка - плохая идея, иногда нарушает инкапсуляцию

Оба имеют одну и ту же проблему - потенциально могут указывать на уничтоженный объект ...

Я бы рекомендовал использовать интеллектуальные указатели во многих ситуациях, когда вам нужно вернуть ссылку / указатель.

Также обратите внимание на следующее:

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

person Community    schedule 15.04.2009
comment
non-const ref не обязательно нарушает инкапсуляцию. рассмотрим vector :: operator [] - person ; 15.04.2009
comment
это особый случай ... поэтому я иногда говорил, хотя я действительно должен требовать БОЛЬШИНУ ВРЕМЕНИ :) - person ; 15.04.2009
comment
Итак, вы говорите, что обычная реализация оператора нижнего индекса - необходимое зло? Я не согласен и не согласен с этим; как я не мудрее. - person Nick Bolton; 15.04.2009
comment
Я этого не говорю, но при неправильном использовании это может быть плохо :))) vector :: at следует использовать везде, где это возможно .... - person ; 15.04.2009
comment
а? vector :: at также возвращает неконстантную ссылку. - person ; 15.04.2009
comment
vector::at выполняет проверку границ, не используйте ее, если не знаете, что это необходимо. - person Miles Rout; 29.10.2014
comment
^ нерелевантно, и в любом случае я бы сказал использовать его, если вы не знаете, что не должны пользовательский ввод) - person underscore_d; 06.12.2015

Это не только не зло, но и иногда необходимо. Например, было бы невозможно реализовать оператор [] для std :: vector без использования возвращаемого значения ссылки.

person Community    schedule 15.04.2009
comment
Ах да, конечно; Думаю, поэтому я начал его использовать; когда я впервые реализовал оператор индекса [], я понял использование ссылок. Я считаю, что это де-факто. - person Nick Bolton; 15.04.2009
comment
Как ни странно, вы можете реализовать operator[] для контейнера без использования ссылки ... и std::vector<bool> это делает. (И создает настоящий беспорядок в процессе) - person Ben Voigt; 31.07.2017
comment
@BenVoigt ммм, почему беспорядок? Возврат прокси-сервера также является допустимым сценарием для контейнеров со сложным хранилищем, которое не сопоставляется напрямую с внешними типами (например, ::std::vector<bool>, о котором вы упомянули). - person Sergey.quixoticaxis.Ivanov; 12.10.2017
comment
@ Sergey.quixoticaxis.Ivanov: Беспорядок в том, что использование std::vector<T> в коде шаблона не работает, если T может быть bool, потому что поведение std::vector<bool> сильно отличается от других экземпляров. Он полезен, но ему следовало дать собственное имя, а не специализацию std::vector. - person Ben Voigt; 12.10.2017
comment
@BenVoight Я согласен с тем, что странное решение сделать одну специализацию действительно особенной, но я чувствовал, что ваш исходный комментарий подразумевает, что возвращение прокси в целом странно. - person Sergey.quixoticaxis.Ivanov; 12.10.2017

Дополнение к принятому ответу:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

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

Чтобы проиллюстрировать это на примере:

struct Foo
{
    Foo(int i = 42) : boo_(i) {}
    immutableint boo()
    {
        return boo_;
    }  
private:
    immutableint boo_;
};

вход в опасную зону:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!
person Community    schedule 21.07.2017

ссылка на возврат обычно используется при перегрузке оператора в C ++ для большого объекта, потому что для возврата значения требуется операция копирования. (при перегрузке perator мы обычно не используем указатель в качестве возвращаемого значения)

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

если вы хотите использовать возврат ссылки, вы можете использовать буфер статического объекта. Например

const max_tmp=5; 
Obj& get_tmp()
{
 static int buf=0;
 static Obj Buf[max_tmp];
  if(buf==max_tmp) buf=0;
  return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
 Obj& res=get_tmp();
 // +operation
  return res;
 }

таким образом вы можете безопасно использовать возвращаемую ссылку.

Но вы всегда можете использовать указатель вместо ссылки для возврата значения в functiong.

person Community    schedule 20.11.2013

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

person Community    schedule 29.07.2014

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

Выделение объекта в функции и возврат его в качестве ссылки или указателя (тем не менее, указатель более безопасен) - плохая идея из-за освобождения памяти в конце функционального блока.

person Community    schedule 05.10.2015

    Class Set {
    int *ptr;
    int size;

    public: 
    Set(){
     size =0;
         }

     Set(int size) {
      this->size = size;
      ptr = new int [size];
     }

    int& getPtr(int i) {
     return ptr[i];  // bad practice 
     }
  };

Функция getPtr может получить доступ к динамической памяти после удаления или даже к нулевому объекту. Что может вызвать исключения недопустимого доступа. Вместо этого должны быть реализованы геттер и сеттер, а размер должен быть проверен перед возвратом.

person Community    schedule 14.03.2017

Функция как lvalue (также известная как возврат неконстантных ссылок) должна быть удалена из C ++. Это ужасно не интуитивно. Скотт Мейерс хотел мин () с таким поведением.

min(a,b) = 0;  // What???

что на самом деле не является улучшением

setmin (a, b, 0);

В последнем даже больше смысла.

Я понимаю, что функция lvalue важна для потоков в стиле C ++, но стоит отметить, что потоки в стиле C ++ ужасны. Я не единственный, кто так думает ... насколько я помню, у Александреску была большая статья о том, как добиться большего, и я считаю, что boost также попытался создать более безопасный метод ввода-вывода.

person Community    schedule 15.04.2009
comment
Конечно, это опасно, и должна быть лучшая проверка ошибок компилятора, но без этого некоторые полезные конструкции не могли бы быть выполнены, например оператор [] () в std :: map. - person j_random_hacker; 16.04.2009
comment
Возврат неконстантных ссылок на самом деле невероятно полезен. vector::operator[] например. Вы бы предпочли написать v.setAt(i, x) или v[i] = x? Последний намного превосходит. - person Miles Rout; 29.10.2014
comment
@MilesRout Я бы выбрал v.setAt(i, x) в любое время. Это НАМНОГО выше. - person scravy; 01.08.2018

Я столкнулся с настоящей проблемой, когда действительно было зло. По сути, разработчик вернул ссылку на объект в векторе. Это было плохо !!!

Полная информация, о которой я писал в январе: http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html

person Community    schedule 15.04.2009
comment
Если вам нужно изменить исходное значение в вызывающем коде, вам необходимо вернуть ref. И это на самом деле не более и не менее опасно, чем возврат итератора к вектору - оба становятся недействительными, если элементы добавляются в вектор или удаляются из него. - person j_random_hacker; 16.04.2009
comment
Эта конкретная проблема была вызвана удержанием ссылки на элемент вектора и последующим изменением этого вектора таким образом, чтобы ссылка была недействительной: стр. 153, раздел 6.2 Стандартной библиотеки C ++: Учебное пособие и справочник - Джосуттис, гласит: Вставка или удаление элементов делает недействительными ссылки, указатели и итераторы, которые ссылаются на следующие элементы. Если вставка вызывает перераспределение, она делает недействительными все ссылки, итераторы и указатели. - person Trent; 19.02.2013

Об ужасном коде:

int& getTheValue()
{
   return *new int;
}

Итак, действительно, указатель памяти потерян после возврата. Но если вы используете shared_ptr вот так:

int& getTheValue()
{
   std::shared_ptr<int> p(new int);
   return *p->get();
}

Память не теряется при возврате и будет освобождена после назначения.

person Community    schedule 23.07.2013
comment
Он теряется, потому что общий указатель выходит за пределы области видимости и освобождает целое число. - person ; 23.07.2013
comment
указатель не теряется, адрес ссылки является указателем. - person dgsomerton; 24.04.2016