Является ли практика возврата ссылочной переменной C ++ злой? - PullRequest
314 голосов
/ 15 апреля 2009

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

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

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

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

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

Ответы [ 16 ]

374 голосов
/ 15 апреля 2009

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

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

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;
}

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

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

62 голосов
/ 15 апреля 2009

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

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

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

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

не будет работать.

44 голосов
/ 15 апреля 2009

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

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

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

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

15 голосов
/ 22 апреля 2016

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

Давайте проанализируем следующие случаи:

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

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!
14 голосов
/ 15 апреля 2009

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

Есть хорошие вещи, которых можно достичь с помощью этого (например, map [name] = "hello world")

10 голосов
/ 15 апреля 2009

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

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

Value& v = thing->getTheValue();

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

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

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

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

7 голосов
/ 15 апреля 2009

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

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

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

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

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

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

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

4 голосов
/ 15 апреля 2009

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

1 голос
/ 22 июля 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!
1 голос
/ 20 ноября 2013

возвращаемая ссылка обычно используется при перегрузке операторов в 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;
 }

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

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

...