Move Semantics & Rvalues
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 83 of 104.
In C++, every expression is either an lvalue (has a persistent identity, can be addressed) or an rvalue (temporary, about to be destroyed). Understanding this distinction unlocks move semantics - a powerful optimization that avoids unnecessary copying.
An rvalue reference, declared with &&, binds specifically to temporary objects. This lets you "steal" resources from objects that are about to disappear anyway:
#include <iostream>
#include <utility>
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : data(new int[s]), size(s) {
std::cout << "Constructed\n";
}
// Move constructor - steals resources
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // Leave source in valid state
other.size = 0;
std::cout << "Moved\n";
}
~Buffer() { delete[] data; }
};
int main() {
Buffer b1(1000);
Buffer b2(std::move(b1)); // Triggers move constructor
}The std::move function doesn't actually move anything - it simply casts an lvalue to an rvalue reference, signaling that you're willing to give up the object's resources. The actual moving happens in the move constructor or move assignment operator.
Move semantics dramatically improve performance when working with resource-heavy objects like containers or strings. Instead of deep copying megabytes of data, you simply transfer pointer ownership - a constant-time operation regardless of size.
Challenge
EasyLet's build a resource-managing DataBuffer class that demonstrates move semantics in action. You'll see how moving resources instead of copying them can dramatically improve efficiency when transferring ownership of dynamically allocated memory.
You'll organize your code across three files:
DataBuffer.h: Define yourDataBufferclass that manages a dynamically allocated array of integers.Your class should have private members for the data pointer (
int*), the size (size_t), and a name (std::string) to help track which buffer is which during operations.Declare the following:
- A constructor that takes a
std::stringname andsize_tsize, allocates the array, and prints:[name] constructed with size [size] - A move constructor that takes an rvalue reference, steals the resources, and prints:
[name] moved from [source_name](where the moved-to buffer takes the source's name) - A destructor that prints
[name] destroyed(orempty destroyedif the buffer was moved from) - A
getSize()method that returns the current size - A
getName()method that returns the buffer's name
Remember to mark your move constructor as
noexceptand leave the source object in a valid empty state (nullptr, size 0, name "empty").- A constructor that takes a
DataBuffer.cpp: Implement all the methods declared in your header. When the destructor runs, only delete the data if the pointer isn't null. Include<iostream>for output.main.cpp: Read two inputs:- A name for your buffer (string)
- A size for your buffer (integer)
Create a
DataBufferwith the given name and size. Then create a second buffer by moving from the first usingstd::move(). After the move, print the state of both buffers:Original: [name] size=[size]New: [name] size=[size]
Include
<utility>forstd::move.
For example, with inputs Alpha and 100:
Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyedWith inputs Buffer and 50:
Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyedNotice how the move constructor transfers ownership of the allocated memory without copying any data. The original buffer is left in an empty but valid state, and when both buffers are destroyed at the end of the program, only the one that still owns the memory actually deletes it.
Cheat sheet
C++ expressions are either lvalues (persistent, addressable) or rvalues (temporary). Move semantics optimize performance by transferring resources from temporary objects instead of copying them.
Rvalue References
Declared with &&, rvalue references bind to temporary objects:
Buffer(Buffer&& other) // Rvalue reference parameterMove Constructor
Steals resources from a temporary object. Mark it noexcept and leave the source in a valid empty state:
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // Leave source valid
other.size = 0;
}std::move
Casts an lvalue to an rvalue reference, signaling willingness to transfer resources. Include <utility>:
Buffer b1(1000);
Buffer b2(std::move(b1)); // Triggers move constructorstd::move doesn't move anything itself - the actual transfer happens in the move constructor or move assignment operator.
Benefits
Move semantics provide constant-time resource transfer regardless of object size, avoiding expensive deep copies of large data structures.
Try it yourself
#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"
int main() {
std::string name;
int size;
std::cin >> name;
std::cin >> size;
// TODO: Create a DataBuffer with the given name and size
// TODO: Create a second buffer by moving from the first using std::move()
// TODO: Print the state of both buffers:
// Original: [name] size=[size]
// New: [name] size=[size]
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container12Modern C++ Features
Move Semantics & RvaluesPerfect ForwardingLambda Expressions In Depthstd::function & std::bindconstexpr and constevalStructured Bindingsoptional, variant, any