Home > Mobile >  Program uses Copy Constructor instead of Move Constructor
Program uses Copy Constructor instead of Move Constructor

Time:11-14

I am trying to understand the concept of copy and move constructors in C . And so trying out different examples. One such example whose output i am unable to understand is given below:

#include <iostream>
#include <vector>
using namespace std;
struct NAME 
{
    NAME()
    {
        std::cout<<"default"<<std::endl;
    }
    NAME(const NAME& )
    {
        std::cout<<"const copy"<<std::endl;
    }
    NAME(NAME& )
    {
        std::cout<<"nonconst copy"<<std::endl;
    }
    NAME(NAME &&)
    {
        std::cout<<"move"<<std::endl;
    }
};
void foo(std::pair<std::string, NAME> ) 
{
    
}
void foo2(std::vector<std::pair<std::string, NAME>> )
{
}
int main()
{
    foo(std::make_pair("an", NAME())); //prints default --> move --> move
    std::cout << "----------------------------------------"<<std::endl;
    
    foo({"an", NAME()});               //prints default --> move
    std::cout << "----------------------------------------"<<std::endl;
    
    foo2({{"an", NAME()}});            //prints default --> move --> const copy
    std::cout << "----------------------------------------"<<std::endl;
    return 0;
}

Case 1: For foo(std::make_pair("an", NAME()));

Output

default
move
move

This is what i think is happening.

Step 1. A temporary of type NAME() is craeted because we've passed it to std::make_pair using the NAME()'s default constructor.

Step 2. One of the std::make_pair's constructor is used which forwards(moves) the the temporary that was created in the 1st step. So NAME()'s move constructor.

Step 3. Finally, since the argument to foo is passed by value, so the std::pair that was created in step 2 is "moved"(not "copied"?) which in turn "moves" the NAME temporary one last time.

Case 2: For foo({"an", NAME()});

Output

default
move

Step 1. A temporary NAME is created.

Step 2. This time since we do not have std::make_pair, std::pair's initializer list constructor(if any) is used to "move" the temporary created in step 1.

Case 3: For foo2({{"an", NAME()}});

Output

default
move
const copy

I have no idea why copy constructor is used instead of move constructor and also why the const version is used instead of nonconst version of copy constructor.

Are my explanation correct. Please correct me where i am wrong in any of my explanation steps in detail.

CodePudding user response:

Case 1: For foo(std::make_pair("an", NAME()));

Output

default
move
move

The crucial issue here is to understand what is the type of the above make_pair invocation. It is not a std::pair<std::string, NAME> as I think you believe, but a std::pair<const char *, NAME> !

You can confirm this by checking that this prints 1.

std::cout << std::is_same_v< decltype(std::make_pair("an", NAME()))
                           , std::pair<const char *, NAME>> << "\n";

So, we observe that:

  1. NAME is constructed using the default constructor. (output: default)
  2. make_pair moves that to return its pair (output: move)
  3. We can not pass that pair to foo, which instead expects std::pair<std::string, NAME>. An implicit pair conversion constructor is called, which creates the string and moves the NAME once again. (output: move)

This is why we observe two moves instead of just one.

Case 2: For foo({"an", NAME()});

Output

default
move

Here we don't call a function to make the pair, so "an" is used to initialize the needed pair directly, which has the right std::pair<std::string, NAME>. We no longer create a pair with the "wrong" type, hence one fewer move.

On case 3: I could not understand why the move constructor is called in case 3. I thought that in order to allow vector to move the NAME, it would have sufficed to mark the move constructor as noexcept, but even in that case the copy is performed. I have no idea right now.

It looks like the move constructor of std::pair is not (conditionally) noexcept as one would expect. That might be the underlying cause.

CodePudding user response:

Elements of std::initializer_list, are const and cannot be moved from.

  • Related