How std::optional library handles emplace operation?

160 views Asked by At

I was trying to create objects for optional variables in-place. When using emplace function in std optional library for a class, the destructor of the class is called. Does that function do copy operation or move operation for that classes.

I called emplace function in three different ways and two of them calls the destructor. What is the difference between them?

Basic class:

class myClass{
private:
   int m_num;
public:
   myClass(int num) : m_num(num){}
   ~myClass(){
      std::cout << "myClass destructed\n";
   }
};

Main:

int main(){
   std::optional<myClass> a;
   std::optional<myClass> b;
   std::optional<myClass> c;

   std::cout << "emplacing a: \n";
   a.emplace(myClass{0});
   std::cout << "emplacing b: \n";
   b.emplace<myClass>(0);
   std::cout << "emplacing c: \n";
   c.emplace(0);
   std::cout << "done\n";
   
   while (true) {};    
}

output:

emplacing a: 
myClass destructed
emplacing b:
myClass destructed
emplacing c:
done
2

There are 2 answers

2
Martin York On BEST ANSWER

If I add print statements to all the compiler generated methods I get the following output:

emplacing a: 
Constructor: 0
Move Constructor: 0
myClass destructed


emplacing b: 
Constructor: 0
Move Constructor: 0
myClass destructed


emplacing c: 
Constructor: 0

done
myClass destructed
myClass destructed
myClass destructed

Note: The following statement are based on having either implicit or explicit move constructors created. Which is what I expect the OP is expecting to have answered by the question. BUT one should note that the implicit move constructors for the OPs myClass have been disabled because the class has a user defined destructor and thus it would use the copy constructor rather than the move constructor.

So you can see in the first two. You are creating an object of myClass which is then being moved into the optional. Then the object that was created is destroyed.

While you third option c simply creates the object emplace into the optional.

I removed the infinte final loop. So you can see that after main() exits all three objects remaining are then destroyed.

So what is happening. Usually emplace is written something like this:

 template<typename... Args>
 void emplace(Args...&& args)
 {
      // In the first two cases the parameter is `myClass` so
      // simply forward the parameter which invokes the move
      // constructor.
      //
      // In the third case the parameter is an int.
      // So forward this to the constructor of myClass there
      // is a constructor that takes an int so use it.
      createThe_T_object_insidethisobject(std::forward<Args>(args)...);
 }

So in your case:

a.emplace(myClass{0});   // Args is myClass
                         // So here you have manually create the myClass
                         // object before even passing it.
                         // This is fine as the forward will then do a perfect forward of the parameter.

b.emplace<myClass>(0);   // Here you are telling the compiler
                         // You are invoking the function using
                         // Args => myClass
                         // So the compiler has to create an R-Value using the values you passed.
                         // Fine. Your user defined constructor works.
                         // Then inside the function you use perfect forwarding.

 c.emplace(0);           // Here Args => int
                         // You forward the parameters to where
                         // the `myclass` object is created.
                         // there is a constructor that takes an
                         // integer so build in place.

PS. just in case it is not obvious. You should be using technique c (as in all C++ questions "usually").

0
JaMiT On

A key thing to keep in mind is that emplace always constructs an object (barring exceptional cases like exceptions being thrown). That is, emplace is invoked, then an object is constructed, then emplace returns.

Terminology note: I will refer to types as "matching" to mean that they are "close enough" for the scenario at hand. This is to account for scenarios where, for example, either T or T& would fit.

a.emplace(myClass{0});

A function's arguments are evaluated before the function is invoked. In this case, the lone function argument, myClass{0}, says to construct a myClass object. This happens before emplace is invoked. Since a myClass object will also be constructed after emplace is invoked, there are two myClass objects constructed in this one line. Only one of these objects survives to the next line, hence the other is destroyed as the line ends.

b.emplace<myClass>(0);

A function's arguments are evaluated before the function is invoked. In this case, the lone function argument, 0, looks simple, but the template argument, myClass, says that the type of this argument must be myClass. So the compiler implicitly constructs a myClass object from the int to form the actual argument to emplace. This happens before emplace is invoked. We're back in the same case as before.

Note: The template arguments specify what emplace will construct an object from. They should match the parameters of the constructor you intend to use. When the template argument is myClass, emplace will use the constructor that matches, namely move or copy construction. This is usually not called "in-place" construction.

c.emplace(0);

In this case, the lone function argument really is simply an int. The template argument for emplace is deduced to match, allowing the value 0 to be passed to emplace without additional processing. There is only one myClass object constructed (which is done after invoking emplace), so there are no extra objects to destroy.


Forcing the construction of a MyClass object before calling emplace() is more work on your part and more work on the compiler's part. Don't bother.