C ++: возвращение с помощью справочных и копических конструкторов

StackOverflow https://stackoverflow.com/questions/2273811

Вопрос

Ссылки в C ++ меня сбивают с толку. :)

Основная идея заключается в том, что я пытаюсь вернуть объект из функции. Я хотел бы сделать это, не возвращая указатель (потому что тогда мне пришлось вручную delete это), и не вызывая конструктора копирования, если это возможно (для эффективности, естественно добавлен: А также потому, что мне интересно, не могу ли я избежать написания конструктора копирования).

Итак, в целом, вот варианты для этого, которые я нашел:

  • Тип возврата функции может быть либо самим классом (MyClass fun() { ... }) или ссылка на класс (MyClass& fun() { ... }).
  • Функция может либо построить переменную в линии возврата (return MyClass(a,b,c);) или вернуть существующую переменную (MyClass x(a,b,c); return x;).
  • Код, который получает переменную, также может иметь переменную любого типа: (MyClass x = fun(); или же MyClass& x = fun();)
  • Код, который получает переменную, может создать новую переменную на лету (MyClass x = fun();) или назначьте его существующей переменной (MyClass x; x = fun();)

И некоторые мысли по этому поводу:

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

Эти результаты настолько непоследовательны, что я чувствую себя полностью запутанным. Итак, что именно здесь происходит? Как мне правильно построить и вернуть объект из функции?

Это было полезно?

Решение

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

Итог - если вам нужно вернуть значение, вернуть значение и не беспокойтесь о каких -либо «расходах».

Другие советы

Рекомендуемое чтение: Эффективный C ++ Скотт Мейерс. Вы находите очень хорошее объяснение по этой теме (и многое другое).

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

Если вы вернетесь по ссылке (или указателю) переменной, которая является локальной (построенной в стеке), вы приглашаете проблемы, потому что объект разрушается при возврате, поэтому в результате у вас есть свисающий ссылку.

Канонический способ построить объект в функции и вернуть его по значению, например:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

Если вы используете это, вам не нужно беспокоиться о проблемах собственности, висящих ссылках и т. Д., И компилятор, скорее всего, оптимизирует дополнительные вызовы конструктора / деструктора, поэтому вам не нужно беспокоиться о производительности.

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

Также технически возможно хранить объект, возвращаемый значением в ссылке, например:

MyClass& x = fun();

Тем не менее, AFAIK, в этом не так уж и много. Тем более, что можно легко передать эту ссылку на другие части программы, которые находятся за пределами текущей области; однако объект, на который ссылаются x это локальный объект, который будет уничтожен, как только вы покинете текущую область. Так что этот стиль может привести к неприятным ошибкам.

прочитать о RVO и NRVO (одним словом эти два обозначают оптимизацию возврата и названные RVO, и являются методами оптимизации, используемых компилятором для того, чтобы делать то, чего вы пытаетесь достичь)

Вы найдете много предметов здесь, на Stackoverflow

Если вы создаете такой объект, как это:

MyClass foo(a, b, c);

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

Так что, если вы хотите вернуть объект в абонента, вы только варианты:

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

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

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

Стандарт позволяет «копировать Elision», что означает, что конструктор копии не должен вызывать при возврате объекта. Это поступает в две формы: оптимизация возврата имени (NRVO) и оптимизация анонимного возврата (обычно только RVO).

Из того, что вы говорите, ваш компилятор реализует RVO, но не NRVO - что означает, что это вероятно несколько более старый компилятор. Большинство текущих компиляторов реализуют оба. Неповрежденный DTOR в этом случае означает, что это, вероятно, что-то вроде GCC 3.4 или Thereabouts-хотя я наверняка не помню эту версию, вокруг была такая ошибка. Конечно, также возможно, что ваши инструменты не совсем правильно, поэтому CTTO, который вы не использовали инструмент, и для этого объекта используется соответствующий DTOR.

В конце концов, вы застряли с одним простым фактом: если вам нужно вернуть объект, вам нужно вернуть объект. В частности, ссылка может дать доступ только к (возможно, модифицированной версии) существующему объекту, но этот объект также должен был быть построен в какой -то момент. Если вы можете изменить какой -то существующий объект, не вызывая проблемы, это нормально и хорошо, продолжайте и сделайте это. Если вам нужен новый объект, отличный и отделен от тех, у вас уже есть, продолжайте и сделайте это-предварительное создание объекта и передача ссылки на него может сделать возврат быстрее, но не сэкономит в целом. Создание объекта имеет примерно ту же стоимость, будь то, сделанная внутри или вне функции. Любой разумно современный компилятор будет включать RVO, поэтому вы не будете заплатить дополнительные затраты за его создание в функции, а затем вернуть его - компилятор просто автоматизирует распределение пространства для объекта, где он будет возвращен, и выполнит функцию Создайте его «на месте», где он все еще будет доступен после возвращения функции.

По сути, возвращение ссылки имеет смысл только в том случае, если объект все еще существует после выхода из метода. Компилятор предупредит вас, если вы вернете ссылку на то, что уничтожается.

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

Ссылки безопаснее, чем указатели, потому что у них разные символы, но за кулисами они являются указателями.

Одно потенциальное решение, в зависимости от вашего варианта использования, состоит в том, чтобы создать объект вне функции по умолчанию, взять в Ссылка на него и инициализируйте указанный объект в функции, как и так:

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

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

Но подумайте о том, почему вы пытаетесь избежать строительства копирования в первую очередь. Действительно ли «расходы» строительства копий влияют на вашу программу, или это случай преждевременной оптимизации?

Вы застряли с любого:

1) Возвращение указателя

Myclass* func () {// некоторые stuf возвращают новый myclass (a, b, c); }

2) вернуть копию объекта myclass func () {return myclass (a, b, c); }

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

Не прямой ответ, а жизнеспособное предложение: вы также можете вернуть указатель, завернутый в Auto_ptr или Smart_ptr. Тогда вы будете контролировать, какие конструкторы и деструкторы вызываются и когда.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top