2016-02-01 2 views
2

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

MyClass function_return_by_value(MyClass par) 
{ 
    return par; 
} 
MyClass b; 
MyClass a = function_return_by_value(b); // (1) 

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

MyClass b; 
function_return_by_value(b); // (2) 

В (1) возвращаемое значение присваивается другой переменной, и RVO может применяться. Но в (2) нет ничего, чтобы получить возвращаемое значение, почему нет оптимизации? Я пробовал gcc 4.8.4 и vC++ 2015, конструктор копирования MyClass дважды вызывается для (2) в обоих компиляторах. Почему производители компиляторов все решают сделать временное уничтожение, даже если временное не используется вообще? Почему они не могут этого избежать?

+3

Зависит от побочных эффектов, вызываемых в вашем конструкторе копирования. –

+1

Вам нужно взять 'par'' const MyClass & ', чтобы предотвратить вторую копию. – PSkocik

+0

Спасибо, @PSkocik, но я думаю, вы должны сначала прочитать мои вопросы. – james

ответ

3

Я пробовал gcc 4.8.4 и vC++ 2015, конструктор копирования MyClass вызывается дважды для (2) в обоих компиляторах.

Потому что так оно и должно работать!

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

+0

Обратите внимание, что из-за копирования, включающего RVO/NRVO, в любом случае нельзя полагаться на побочные эффекты конструкторов и деструкторов. В условиях, описанных в копировании, компилятор разрешает не вызывать конструкторы или деструкторы, даже если они имеют наблюдаемые побочные эффекты. –

4

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

Это позволило бы следующую программу для вывода ничего, что очень явно неправильно:

#include <iostream> 

struct A 
{ 
    A() { std::cout << "Booyah\n"; } 
}; 

int main() 
{ 
    A a; 
} 
1

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

#include <iostream> 

class Krowa 
{ 
public: 
     Krowa() {std::cout << "Default krowa\n";} 
     Krowa(Krowa const &) {std::cout << "Copied Krowa\n";} 
}; 

Krowa fun1(Krowa const & krowa) 
{ 
     return krowa; 
} 

Krowa fun2() 
{ 
     return Krowa{}; 
} 

int main() 
{ 
     fun1(Krowa{}); 
     fun2(); 
     return 0; 
} 

Выход

По умолчанию krowa
Скопировано Krowa
По умолчанию krowa

fun1 возвращаемое значение является копией параметра, поэтому конструктор копирования вызывается , fun2 Возвращаемое значение - построенный по умолчанию объект, поэтому вызывается по умолчанию construcotr.

1

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

Теперь предположим, что компилятор знает, что в пределах одной единицы перевода возвращаемое значение никогда не используется. Разумеется, компилятор может затем оптимизировать функцию, чтобы никогда не строить объект. Но что, если позже, в другой блок компиляции, эта функция вызывается, а результат -? Оптимизированная версия функции не будет работать, потому что она ничего не возвращает!

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

ОК, предположим, что конструктор и деструктор объекта видны, когда функция скомпилирована. Тогда, если компилятор может доказать, что побочных эффектов нет, тогда теоретически можно применить описанную выше оптимизацию. Учитывая строгие требования к этой оптимизации (видимые c'tor и d'tor без побочных эффектов) и неблагоприятные эффекты (две версии всех возвращающих функций значений увеличивают время компиляции и, возможно, увеличивают двоичный размер), я сомневаюсь в этом тип оптимизации был рассмотрен всерьез.

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

void function_that_has_side_effects(const MyClass& par): 

MyClass function_return_by_value(MyClass par) 
{ 
    function_that_has_side_effects(par); 
    return par; 
} 

Теперь, если вы хотите, побочные эффекты, но не нужно возвращаемое значение, просто назовите function_that_has_side_effects.

MyClass b; 
function_that_has_side_effects(b); // (2) 
Смежные вопросы