Categories: Programming

Playing with move semantics in C++ – Part 2

(Playing with move semantics in C++ – Part 1)

In this second part, we take a look at what happens when an object stores a reference to another one, effectively not controlling its lifetime. An rvalue reference is required as an argument in order to convey the “I am taking control of the object’s lifetime” semantics. Of course, a shared_ptr or unique_ptr could have also been used and some might prefer it, especially if access to the referred object is still required outside the context of the newly constructed one.

We assume the same ExpensiveToCopy class from part 1. We first introduce the KeepsEtcRef class, instances of which need to refer to an ExpensiveToCopy instance.

class KeepsEtcRef {
 public:
  KeepsEtcRef() = delete;
  KeepsEtcRef(ExpensiveToCopy& etcArg): etc(etcArg) { cout << "KeepsEtcRef: costructor" << endl; }
  ~KeepsEtcRef() { cout << "KeepsEtcRef: destructor" << endl; } 
  void print() { etc.print(); } 
 private:
  ExpensiveToCopy& etc;
};

Let’s instantiate both classes making sure that the ExpensiveToCopy instance doesn’t get destructed while the KeepsEtcRef instance is still around.

{
  cout << "KeepsEtcRef" << endl;
  ExpensiveToCopy etc;
  KeepsEtcRef k(etc);
  k.print();
}
// output
KeepsEtcRef
ExpensiveToCopy (1.0): constructor
KeepsEtcRef: costructor
ExpensiveToCopy (1.0): contains 2 messages
KeepsEtcRef: destructor
ExpensiveToCopy (1.0): destructor

What if the Large instance went away somehow?

{
  cout << "KeepEtcRef outlives ExpensiveToCopy" << endl;
  ExpensiveToCopy* etc = new ExpensiveToCopy();
  KeepsEtcRef k(*etc);
  delete etc;
  k.print();
}
KeepEtcRef outlives ExpensiveToCopy
ExpensiveToCopy (1.0): constructor
KeepsEtcRef: costructor
ExpensiveToCopy (1.0): destructor
ExpensiveToCopy (0.0): contains 0 messages
KeepsEtcRef: destructor

No surprise! The data is gone. The KeepsEtcRef object outlives that to which it maintains a reference. While the program doesn’t crash in this case (since the vector object still exists), something more catastrophic could have happened. Resources were released. We could have ended up with an ugly crash. Can move help us? Let’s rewrite the container class…

class KeepsEtc {
 public:
  KeepsEtc() = delete;
  KeepsEtc(ExpensiveToCopy&& etc): large(move(etc)) { cout << "KeepsEtc: costructor" << endl; }
  ~KeepsEtc() { cout << "KeepsEtc: destructor" << endl; }
  void print() { large.print(); } 
 private:
  ExpensiveToCopy large;
};

Notice the constructor requiring an rvalue, instead of a reference, as an argument.

{
  cout << "KeepsEtc" << endl;
  ExpensiveToCopy etc;
  KeepsEtc k(move(etc));
  k.print();
}
// output
KeepsEtc
ExpensiveToCopy (1.0): constructor
ExpensiveToCopy (1.1): move constructor (cheap)
KeepsEtc: costructor
ExpensiveToCopy (1.1): contains 2 messages
KeepsEtc: destructor
ExpensiveToCopy (1.1): destructor
ExpensiveToCopy (1.0): destructor

No surprises here. Worked like a charm this time 🙂 We do end up creating a copy of the object but we use the move constructor, which is cheap. What would have happened if we were to explicitly destruct the ExpensiveToCopy object after the move (i.e. not waiting for it to go out of context)?

{
  cout << "KeepsEtc outlives ExpensiveToCopy" << endl;
  ExpensiveToCopy* etc = new ExpensiveToCopy();
  KeepsEtc kl(move(*etc));
  delete etc;
  kl.print();
}
KeepsEtc outlives ExpensiveToCopy
ExpensiveToCopy (1.0): constructor
ExpensiveToCopy (1.1): move constructor (cheap)
KeepsEtc: costructor
ExpensiveToCopy (1.0): destructor
ExpensiveToCopy (1.1): contains 2 messages
KeepsEtc: destructor
ExpensiveToCopy (1.1): destructor

As expected, it worked just fine. That’s because the KeepsEtc was given a copy of the ExpensiveToCopy object, which was cheaply constructed using move().

What would happen if instead of destructing, we were to call a method on the original ExpensiveToCopy object?

{
  cout << "Call to etc after it was moved" << endl;
  ExpensiveToCopy etc;
  KeepsEtc kl(move(etc));
  etc.print();
}
// output
Call to etc after it was moved
ExpensiveToCopy (1.0): constructor
ExpensiveToCopy (1.1): move constructor (cheap)
KeepsEtc: costructor
ExpensiveToCopy (1.0): contains 0 messages
KeepsEtc: destructor
ExpensiveToCopy (1.1): destructor
ExpensiveToCopy (1.0): destructor

Again, I was lucky the process didn’t crash. The implementation of the vector move constructor didn’t leave the vector data member in an undefined state. However, the data was moved.

All code is available at… https://github.com/savas/playground/tree/master/cpp-move-semantics

(Thanks to Viswanath Sivakumar for spotting a copy-paste mistake with the last code snippet. Now fixed).

Savas Parastatidis

Savas Parastatidis works at Amazon as a Sr. Principal Engineer in Alexa AI'. Previously, he worked at Microsoft where he co-founded Cortana and led the effort as the team's architect. While at Microsoft, Savas also worked on distributed data storage and high-performance data processing technologies. He was involved in various e-Science projects while at Microsoft Research where he also investigated technologies related to knowledge representation & reasoning. Savas also worked on language understanding technologies at Facebook. Prior to joining Microsoft, Savas was a Principal Research Associate at Newcastle University where he undertook research in the areas of distributed, service-oriented computing and e-Science. He was also the Chief Software Architect at the North-East Regional e-Science Centre where he oversaw the architecture and the application of Web Services technologies for a number of large research projects. Savas worked as a Senior Software Engineer for Hewlett Packard where he co-lead the R&D effort for the industry's Web Service transactions service and protocol. You can find out more about Savas at https://savas.me/about

Recent Posts

BrainExpanded – The Timeline

See "BrainExpanded - Introduction" for context on this post. Notes and links Over the years,…

2 days ago

BrainExpanded – Introduction

This is the first post, in what I think is going to be a series,…

2 days ago

Digital twin follow up

Back in February, I shared the results of some initial experimentation with a digital twin.…

2 weeks ago

Digital Twin (my playground)

I am embarking on a side project that involves memory and multimodal understanding for an…

10 months ago

“This is exactly what LLMs are made for”

I was in Toronto, Canada. I'm on the flight back home now. The trip was…

1 year ago

AI is enhancing me

AI as an enhancer of human abilities.

1 year ago