Sam Coutteau
Sam Coutteau

Reputation: 367

Understanding when a compiler copies, moves or constructs in place

I've been reading up on copy and move semantics to improve my knowledge of when to use T vs T const & vs T &&

I wrote a small bit of test code to verify my intuition

#include <iostream>
#include <utility>
#include <vector>

template< int i >
struct Example
{
    Example(){ std::cout << "("<< i<<") was made\n"; }
    Example( Example const & ){ std::cout << "("<< i<<") was copied\n"; }
    Example( Example && ){ std::cout << "("<< i<<") was moved\n"; }
    ~Example(){ std::cout << "("<< i<<") was destroyed\n"; }
    
    std::vector< int > x = {1,2,3,4};
};

template< int i >
struct Test
{
    Example< i > example;
    
    // Test( Example< i > example ): example( example ) {}
    
    // Test( Example< i > const & example ): example( example ) {}
    // Test( Example< i > && example ): example( std::move(example) ) {}
};

int main()
{
    Example<1> example1;
    Example<2> example2;
    
    std::cout << "Test1\n";
    Test test1{ example1 };
    std::cout << "Test2\n";
    Test test2{ std::move(example2) };
    std::cout << "Test3\n";
    Test test3{ Example<3>() };
    
    std::cout << "Tests done \n";
}

This produces the output I expect:

(1) was made
(2) was made
Test1
(1) was copied
Test2
(2) was moved
Test3
(3) was made
Tests done 
(3) was destroyed
(2) was destroyed
(1) was destroyed
(2) was destroyed
(1) was destroyed

g++ -std=c++20 -O3

Adding explicit constructor to Test

    Test( Example< i > const & example ): example( example ) {}
    Test( Example< i > && example ): example( std::move( example ) ) {}

Still fits my intuition, as now that there is only a copy and move, test3 now resorts to using a move.

However when only defining the following constructor

    Test( Example< i > example ): example( example ) {}

I get

(1) was made
(2) was made
Test1
(1) was copied
(1) was copied
(1) was destroyed
Test2
(2) was moved
(2) was copied
(2) was destroyed
Test3
(3) was made
(3) was copied
(3) was destroyed
Tests done 
(3) was destroyed
(2) was destroyed
(1) was destroyed
(2) was destroyed
(1) was destroyed

Whilst I can understand the extra copy of example1, it still seems strange to me. However it does not make sense in combination with what happends to example3 I would either also expect two copies or a move.

I was always under the impression that:

Put with this example this does not seem the case, as given in the example Example contains a vector, thus a copy is not the lightest operation there is and a move would be prefereable.

So my question summarized is:

To avoid expensive copies and to allow for constructing in place, when should you use T, T&, T const & and T&& ( maybe T const && ).

This is also in the context of functions not just constructors.

( Note: with construct in place I mean the phenomina as shown for test3 initialy where the temporary object is neither moved or copied into test3 )

Upvotes: -4

Views: 83

Answers (1)

Perl99
Perl99

Reputation: 153

In short, prefer reference T&, unless the type is small, like int. If the type is not going to be used by the caller code, consider T&& + std::move().

The simplicity of the code is more important that speed, unless you measure that this is a hot spot of your program.

C++ Core guidelines that you linked in the comment: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#fcall-parameter-passing

Upvotes: 2

Related Questions