Search code examples
c++c++11function-calls

Understanding function call in context of temporary objects


Look at this simple code:

class A
{};

A f(A a)
{
    return a;
}

int main(void)
{
    A a;
    A b = f(a);
    return 0;
}

It creates a local variable a, calls a function f() and assigns its return value to another variable b. But I'd like to know what happens during the function call.

Could someone describe to be me, step by step, what objects (temporary or otherwise) are created during the process, what constructors, destructors and assign/move operators are called and when?


Solution

  • When in doubt bring out the Noisy class:

    struct Noisy {
        Noisy() { std::cout << "Default construct" << std::endl; }
        Noisy(const Noisy&) { std::cout << "Copy construct" << std::endl; }
        Noisy(Noisy&&) { std::cout << "Move construct" << std::endl; }
        Noisy& operator=(const Noisy&) { std::cout << "Copy assignment" << std::endl; return *this; }
        Noisy& operator=(Noisy&&) { std::cout << "Move assignment" << std::endl; return *this; }
        ~Noisy() { std::cout << "Destructor" << std::endl; }
    };
    
    Noisy f(Noisy a) {
        return a;
    }
    
    int main(void) {
        Noisy a;
        Noisy b = f(a);
    }
    

    Compiled with gcc-4.9.1 using options g++ -fno-elide-constructors -std=c++11 t.cc gives output:

    Default construct // 1. 'a' is default constructed.
    Copy construct    // 2. Local argument 'a' in function 'f' is copied.
    Move construct    // 3. Return value is move constructed (*see note below).
    Move construct    // 4. 'b' is move constructed from return value.
    Destructor        // 5. Local argument 'a' is destroyed.
    Destructor        // 6. Return value is destroyed.
    Destructor        // 7. 'b' is destroyed.
    Destructor        // 8. 'a' is destroyed.
    

    Note: Even though local argument a is an lvalue, the compiler knows it's about to go out of scope and considers it as an rvalue.

    Compiling without option -fno-elide-constructors will enable compiler copy elision optimizations and yields output:

    Default construct // 1. 'a' is default constructed.
    Copy construct    // 2. Local argument 'a' in function 'f' is copied.
    Move construct    // 3. 'b' is move constructed from argument 'a' (elision).
    Destructor        // 4. Local argument 'a' is destroyed.
    Destructor        // 5. 'b' is destroyed.
    Destructor        // 6. 'a' is destroyed.
    

    Compiling with -std=c++03 i.e. C++03 will result in all moves being replaced with copies.

    For more info about copy elision see here: What are copy elision and return value optimization?