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

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

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

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

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

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

Ответ 1

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

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

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

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

Сводка: нормально возвращать ссылку, если срок жизни объекта не будет завершен после вызова.

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Проанализируем следующие случаи:

Неверное использование

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!

Ответ 6

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

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

Value& v = thing->getTheValue();

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

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

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

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

Ответ 7

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

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

  • неконстантная ссылка - идея иногда разрывает инкапсуляции

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

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

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

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

Ответ 8

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

Ответ 9

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

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

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

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.

Ответ 10

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

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!

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

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

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

setmin (a, b, 0);

Последнее даже имеет больше смысла.

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

Ответ 14

    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 может получить доступ к динамической памяти после удаления или даже к нулевому объекту. Это может привести к неправильным исключениям. Вместо этого должен быть внедрен геттер и сеттер, а размер проверен перед возвратом.

Ответ 15

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

Полные подробности, о которых я писал в Janurary: http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html

Ответ 16

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

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

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

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

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