2012-06-06 6 views
5

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

class A 
{ 
    public: 
    A() { cout<<"constructor, "; } 
    A (const A&) { cout<<"copy-constructor, "; } 
    A& operator = (const A&) { cout<<"assignment, "; } 
    ~A() { cout<<"destructor, "; } 
}; 
    const A f(A x) 
    {A y; cout<<"f, "; return y;} 

    const A g(A x) 
    {A y; cout<<"g, "; return x;} 

main() 
{ 
    A a; 
    A b = f(a); 
    A c = g(a); 
} 

Теперь, когда я исполняю линию A b = f(a);, он выводит:

copy-constructor, constructor, f, destructor, который прекрасно согласен с тем, что объект y в f() создается непосредственно в пункте назначения, т.е. в ячейке памяти объекта b, и не задействован времен.

Хотя, когда я исполняю линию A c = g(a);, она выводит:

copy-constructor, constructor, g, copy-constructor, destructor, destructor,.

Итак, вопрос в том, почему в случае g() невозможно создать объект в ячейке памяти c, как это произошло при вызове f()? Почему он вызывает дополнительный экземпляр-конструктор (который, я полагаю, из-за участия временного) во втором случае?

+0

Если вы хотите, чтобы компилятор выполнял оптимизацию, вам придется скомпилировать с включенными оптимизациями. –

+0

Я не думаю, что это имеет какое-то отношение к оптимизации компилятора, как я уже пробовал. – cirronimbo

ответ

3

Разница в том, что в случае g вы возвращаете значение, переданное функции. Стандарт явно указывает, при каких условиях копия может быть отменена в 12.8p31, и она не включает в себя возврат копии из аргумента функции.

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

Я начал недолговечный блог некоторое время назад (я ожидал, что у меня будет больше времени ...), и я написал несколько статей о NRVO и скопировал их, чтобы помочь выяснить это (или нет, кто знает :)) :

Value semantics: NRVO

Value semantics: Copy elision

+0

Большое спасибо. Ваше «[Un] определенное поведение» решило многие из моих сомнений в определенно «четко определенном» манере :) Но у вас есть еще несколько сомнений, если вы можете сказать: 1. В NRVO, когда вы использовал «bool», чтобы выбрать «тип x и y», который «type» возвращает, кажется, что мой компилятор неспособен сделать elision (я также сомневаюсь в других). – cirronimbo

+0

2. В моем коде, если я связываю ссылку к временному, т.е. «A & b = f (a)», то происходит то, что объем объекта (предположительно временный, возвращаемый f (a)) увеличивается до конечной скобки main. Таким образом, это противоречит двум вещам: 1. Как вы упомянули в своем блоге, что обращение с временным адресом является незаконным, но мы делаем это здесь. 2. Как может длиться так долго? – cirronimbo

+0

3. Является ли это тем, что области локальных объектов-членов функции и ее аргументы различны.Как когда я делал что-то вроде «A a; A b; b = g (a);» затем в строке «b = g (a)» деструктор локального объекта вызывается перед оператором присваивания и аргументом аргумента после назначения. – cirronimbo

7

Проблема в том, что во втором случае вы возвращаете один из параметров. Учитывая, что обычно копирование параметров происходит на сайте вызывающего, а не внутри функции (main в этом случае), компилятор делает копию, а затем вынужден копировать ее снова, как только она вводит g().

От http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

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

+0

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

4

Вот небольшая модификация кода, который поможет вам прекрасно понять, что там происходит:

class A{ 
public: 
    A(const char* cname) : name(cname){ 
     std::cout << "constructing " << cname << std::endl; 
    } 
    ~A(){ 
     std::cout << "destructing " << name.c_str() << std::endl; 
    } 
    A(A const& a){ 
     if (name.empty()) name = "*tmp copy*"; 
     std::cout 
      << "creating " << name.c_str() 
      << " by copying " << a.name.c_str() << std::endl; 
    } 
    A& operator=(A const& a){ 
     std::cout 
      << "assignment (" 
       << name.c_str() << " = " << a.name.c_str() 
      << ")"<< std::endl; 
     return *this; 
    } 
    std::string name; 
}; 

Вот использование этого класса:

const A f(A x){ 
    std::cout 
     << "// renaming " << x.name.c_str() 
     << " to x in f()" << std::endl; 
    x.name = "x in f()"; 
    A y("y in f()"); 
    return y; 
} 

const A g(A x){ 
    std::cout 
     << "// renaming " << x.name.c_str() 
     << " to x in f()" << std::endl; 
    x.name = "x in g()"; 
    A y("y in g()"); 
    return x; 
} 

int main(){ 
    A a("a in main()"); 
    std::cout << "- - - - - - calling f:" << std::endl; 
    A b = f(a); 
    b.name = "b in main()"; 
    std::cout << "- - - - - - calling g:" << std::endl; 
    A c = g(a); 
    c.name = "c in main()"; 
    std::cout << ">>> leaving the scope:" << std::endl; 
    return 0; 
} 

и вот при компиляции без какой-либо оптимизации:

constructing a in main() 
- - - - - - calling f: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in f() 
creating *tmp copy* by copying y in f() 
destructing y in f() 
destructing x in f() 
- - - - - - calling g: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in g() 
creating *tmp copy* by copying x in g() 
destructing y in g() 
destructing x in g() 
>>> leaving the scope: 
destructing c in main() 
destructing b in main() 
destructing a in main() 

Выведенный вами вывод - это выход программы, составленной с помощью Named Return Value Optimization. В этом случае компилятор пытается устранить избыточный конструктор копирования и вызовы Destructor, что означает, что при возврате объекта он попытается вернуть объект без создания избыточной копии. Вот выход с NRVO включен:

constructing a in main() 
- - - - - - calling f: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in f() 
destructing x in f() 
- - - - - - calling g: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in g() 
creating *tmp copy* by copying x in g() 
destructing y in g() 
destructing x in g() 
>>> leaving the scope: 
destructing c in main() 
destructing b in main() 
destructing a in main() 

В первом случае *tmp copy* копирования y in f() не создается, так как NRVO сделал свою работу. Во втором случае, если NRVO не может быть применен, потому что в этой функции объявлен другой кандидат на слот возврата.Для получения дополнительной информации см .: C++ : Avoiding copy with the "return" statement :)

+0

Да, я знаю это, и я сделал это и в своем коде, чтобы узнать, что именно происходит (хотя я опубликовал упрощенную версию кода, в которой подчеркивается только моя проблема). И этот код не имеет никакого отношения к вопросу, который я задаю. То, о чем меня спрашивали, было ПРИЧИНЫ для того, что происходит, а не для самого себя. В любом случае, спасибо за проявленную озабоченность :) – cirronimbo

+0

@cirronimbo: Посмотрите мой ответ сейчас, он объясняет, что происходит с включенным NRVO, а также объясняет, почему я предложил вам этот вопрос. – LihO

0

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

A a; 
A c = a; 

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

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

Смежные вопросы