[2026] C++ Move Semantics: Copy vs Move Explained

[2026] C++ Move Semantics: Copy vs Move Explained

이 글의 핵심

C++11 move semantics: rvalue references, std::move, Rule of Five, noexcept move constructors—copy vs move performance and safe usage patterns.

Introduction

Move semantics are a feature introduced in C++11 that allows moving the resources of an object without copying them. Objects with large copy costs (vectors, strings, etc.) can be passed efficiently. Why do you need it?:

  • Performance: Improve performance by moving instead of copying
  • Ownership Transfer: Explicitly transfer resource ownership
  • Uncopyable type: Passing a non-copyable type such as unique_ptr
  • Temporary object optimization: Resource reuse of temporary objects 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Copy: slow (deep copy)
std::vector<int> v1(1000000);// Allocate 1 million elements
std::vector<int> v2 = v1;// Copy all 1 million elements
// movement:
// 1. Allocate new memory for v2 (1 million * sizeof(int))
// 2. Copy all elements from v1 to v2
// 3. Both v1 and v2 have independent memory
// Cost: O(n) time, O(n) memory
// ✅ Move: Fast (shallow copy)
std::vector<int> v1(1000000);// Allocate 1 million elements
std::vector<int> v2 = std::move(v1);// Copy only the pointer (move the internal buffer)
// movement:
// 1. Copy the internal pointer of v1 to v2 (3 pointers: data, size, capacity)
// 2. Set the pointer of v1 to nullptr (invalidate)
// 3. v2 owns v1's memory
// Cost: O(1) time, no additional memory

1. Lvalue vs Rvalue

Basic concepts

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

int x = 10;// x is an lvalue (has a name, has an address)
// Can be referenced multiple times, memory location confirmed
int y = x + 5;// x+5 is rvalue (temporary value, no address)
// Disappears immediately after expression evaluation, only used once
int* ptr = &x;// ✅ OK: You can get the address of the lvalue
// x is stored in memory
// int* ptr2 = &(x+5);// ❌ Error: Unable to get address of rvalue
// x+5 is temporary register value, no memory address

Key points:

  • lvalue: has a name, can be used multiple times, has an address
  • rvalue: Temporary value, disappears at the end of the expression, cannot have an address

lvalue and rvalue examples

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <string>
#include <iostream>
std::string getName() {
return "Alice";
}
int main() {
int x = 10;           // x: lvalue
int y = x + 5;        // x+5: rvalue
int z = std::move(x); // std::move(x): rvalue
std::string s1 = "hello";
std::string s2 = s1;           // s1: lvalue
std::string s3 = s1 + " world"; // s1 + " world": rvalue
std::string name = getName();  // getName(): rvalue
return 0;
}

rvalue reference

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

int x = 10;
// lvalue reference (T&): Only lvalues ​​can be bound
int& ref1 = x;        // ✅ OK: x is lvalue
// int& ref2 = 10;// ❌ Error: 10 is an rvalue (temporary value)
// lvalue references can only be values ​​with memory addresses
// rvalue reference (T&&): Only rvalues ​​can be bound
// int&& ref3 = x;// ❌ Error: x is an lvalue
// rvalue references can only be temporary values
int&& ref4 = 10;// ✅ OK: 10 is rvalue (temporary value)
// rvalue references extend the lifetime of temporary values
// const lvalue reference (const T&): any possible (special rule)
const int& ref5 = x;// ✅ OK: lvalue binding
const int& ref6 = 10;// ✅ OK: rvalue binding (extending temporary value lifetime)
// const reference is the only lvalue reference that can receive a temporary value

2. Copy vs Move

Copy (inefficient)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <cstring>
class String {
private:
char* data;
size_t size;
public:
String(const char* str) {
size = strlen(str);
data = new char[size + 1];
strcpy(data, str);
std::cout << "constructor" << std::endl;
}
~String() {
delete[] data;
std::cout << "destructor" << std::endl;
}
// copy constructor
String(const String& other) {
size = other.size;
data = new char[size + 1];
strcpy(data, other.data);// deep copy
std::cout << "copy constructor" << std::endl;
}
void print() const {
std::cout << data << std::endl;
}
};
int main() {
String s1("Hello");
String s2 = s1;// Copy occurs (memory allocation + copy)
s2.print();
return 0;
}

output of power: 아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constructor
copy constructor
Hello
destructor
destructor

Movement (efficient)

#include <iostream>
#include <cstring>
class String {
private:
char* data;
size_t size;
public:
String(const char* str) {
size = strlen(str);
data = new char[size + 1];
strcpy(data, str);
std::cout << "constructor" << std::endl;
}
~String() {
delete[] data;
std::cout << "destructor" << std::endl;
}
// copy constructor
String(const String& other) {
size = other.size;
data = new char[size + 1];
strcpy(data, other.data);
std::cout << "copy constructor" << std::endl;
}
// Move constructor: Receives an rvalue reference (String&&)
// noexcept: ensures no exception is thrown (performance optimization)
String(String&& other) noexcept {
// 1. Copy only the pointer (shallow copy)
data = other.data;// Get other's memory
size = other.size;
// 2. Invalidate the original (important!)
// so as not to delete when other's destructor is called
other.data = nullptr;// original pointer to nullptr
other.size = 0;
std::cout << "move constructor" << std::endl;
// Result: no memory allocation, only pointer movement (very fast)
}
void print() const {
if (data) std::cout << data << std::endl;
else std::cout << "(empty)" << std::endl;
}
};
int main() {
String s1("Hello");
String s2 = std::move(s1);// Move (copy only the pointer, fast!)
s2.print();  // Hello
s1.print();// (empty) - s1 is no longer available
return 0;
}

output of power: 아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constructor
move constructor
Hello
(empty)
destructor
destructor

3. std::move

Default Enabled

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <utility>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
// Copy: Copy all elements to new memory
std::vector<int> v2 = v1;// Copy all elements of v1
// v1 and v2 have independent memory
std::cout << "v1 size: " << v1.size() << std::endl;// 5 (maintain)
std::cout << "v2 size: " << v2.size() << std::endl;// 5 (new allocation)
// Move: Move only the internal buffer pointer
// std::move(v1): Cast v1 to rvalue
// call move constructor → transfer internal buffer of v1 to v3
std::vector<int> v3 = std::move(v1);// Move v1's internal buffer to v3
// v1 is valid but empty (moved-from state)
std::cout << "v1 size: " << v1.size() << std::endl;// 0 (empty)
std::cout << "v3 size: " << v3.size() << std::endl;// 5 (buffer owned by v1)
// NOTE: v1 is deprecated (destructor call is safe)
return 0;
}

Note: std::move does not actually move, it only casts to an rvalue!

std::move implementation

다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Conceptual implementation of std::move
// Actually just a simple cast (no movement!)
template<typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept {
// std::remove_reference<T>::type: Remove reference from T
// If T is int& → int
// If T is int&& → int
//
// static_cast<...&&>: Cast to rvalue reference
// This casting causes the move constructor to be called
//
// Bottom line: std::move doesn't move, it just marks it as "movable"
// Actual movement is performed by the movement constructor/assignment operator
return static_cast<typename std::remove_reference<T>::type&&>(arg);
}

4. Rule of Five

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
class Resource {
private:
int* data;
public:
// 1. Constructor
Resource(int value) : data(new int(value)) {
std::cout << "constructor: " << *data << std::endl;
}
// 2. Destructor
~Resource() {
std::cout << "destructor: " << (data ? std::to_string(*data) : "null") << std::endl;
delete data;
}
// 3. Copy constructor
Resource(const Resource& other) {
data = new int(*other.data);
std::cout << "copy constructor: " << *data << std::endl;
}
// 4. Copy assignment operator
Resource& operator=(const Resource& other) {
if (this != &other) {
delete data;
data = new int(*other.data);
std::cout << "copy assignment: " << *data << std::endl;
}
return *this;
}
// 5. Move constructor
Resource(Resource&& other) noexcept {
data = other.data;
other.data = nullptr;
std::cout << "move constructor" << std::endl;
}
// 6. Move assignment operator
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
std::cout << "move assignment" << std::endl;
}
return *this;
}
int get() const { return data ? *data : 0; }
};
int main() {
Resource r1(10);
Resource r2 = r1;// copy constructor
Resource r3 = std::move(r1);// move constructor
Resource r4(20);
r4 = r2;// copy assignmentResource r5(30);
r5 = std::move(r4);// move assignment
return 0;
}

output of power: 아래 코드는 code를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

Created by: 10
Copy Constructor: 10
move constructor
Created by: 20
Copy Assignment: 10
Created by: 30
transfer assignment
Destructor: 10
Destructor: 10
destructor: null
Destructor: 10
destructor: null

5. Practical example

Example 1: Vector optimization

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iostream>
class BigObject {
private:
std::vector<int> data;
public:
BigObject(int size) : data(size, 0) {
std::cout << "Constructor: " << size << "elements" << std::endl;
}
BigObject(const BigObject& other) : data(other.data) {
std::cout << "Copy constructor: " << data.size() << "elements" << std::endl;
}
BigObject(BigObject&& other) noexcept : data(std::move(other.data)) {
std::cout << "Move constructor: " << data.size() << "elements" << std::endl;
}
};
std::vector<BigObject> createObjects() {
std::vector<BigObject> result;
result.push_back(BigObject(1000));// call move constructor
result.push_back(BigObject(2000));
return result;// RVO or move
}
int main() {
auto objects = createObjects();
std::cout << "complete" << std::endl;
return 0;
}

Example 2: Smart pointer movement

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <memory>
#include <iostream>
void process(std::unique_ptr<int> ptr) {
std::cout << "Value: " << *ptr << std::endl;
}
int main() {
auto ptr1 = std::make_unique<int>(42);
// process(ptr1);// Error: unique_ptr cannot be copied
process(std::move(ptr1));// OK: Move
//ptr1 is now nullptr
if (!ptr1) {
std::cout << "ptr1 is empty" << std::endl;
}
return 0;
}

output of power:

Value: 42
ptr1 is empty

Example 3: Perfect Forwarding

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <utility>
void process(int& x) {
std::cout << "lvalue version: " << x << std::endl;
}
void process(int&& x) {
std::cout << "rvalue version: " << x << std::endl;
}
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));// perfect delivery
}
int main() {
int x = 10;
wrapper(x);// call lvalue version
wrapper(20);// rvalue version call
return 0;
}

output of power:

lvalue version: 10
rvalue version: 20

6. Frequently occurring problems

Problem 1: Use after move

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iostream>
int main() {
// ❌ Dangerous code
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);
std::cout << v1.size() << std::endl;// 0 (or undefined behavior)
//v1[0];// Undefined behavior!
// ✅ Correct code
std::vector<int> v3 = {4, 5, 6};
std::vector<int> v4 = std::move(v3);
// v3 is no longer used
return 0;
}

Problem 2: const objects cannot be moved

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <iostream>
int main() {
// ❌ No movement
const std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);// Copied!
std::cout << "v1 size: " << v1.size() << std::endl;// 3 (still valid)
// ✅ Without const
std::vector<int> v3 = {1, 2, 3};
std::vector<int> v4 = std::move(v3);// moved
std::cout << "v3 size: " << v3.size() << std::endl;// 0
return 0;
}

Issue 3: Exception in move constructor

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <stdexcept>
#include <vector>
// ❌ Danger: noexcept
class Bad {
public:
Bad(Bad&& other) { // noexcept
throw std::runtime_error("error");
}
};
// ✅ Add noexcept
class Good {
std::vector<int> data;
public:
Good(Good&& other) noexcept : data(std::move(other.data)) {
//no exception thrown
}
};
int main() {
std::vector<Good> v;
v.reserve(10);// use noexcept move constructor
return 0;
}

Reason: When std::vector has a noexcept move constructor when reserve(), it is moved, otherwise it is copied.

Issue 4: Using std::move on return

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
// Use ❌ move: Interrupt RVO
std::vector<int> createVector1() {
std::vector<int> v = {1, 2, 3};
return std::move(v);// RVO obstruction
}
// ✅ Just return: apply RVO
std::vector<int> createVector2() {
std::vector<int> v = {1, 2, 3};
return v;// RVO
}
int main() {
auto v1 = createVector1();
auto v2 = createVector2();
return 0;
}

7. Performance comparison

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <chrono>
#include <vector>
#include <iostream>
void testCopy() {
std::vector<int> v1(1000000, 42);
auto start = std::chrono::high_resolution_clock::now();
std::vector<int> v2 = v1;// copy
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "copy: " << duration.count() << "μs" << std::endl;
}
void testMove() {
std::vector<int> v1(1000000, 42);
auto start = std::chrono::high_resolution_clock::now();
std::vector<int> v2 = std::move(v1);// movement
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "move: " << duration.count() << "μs" << std::endl;
}
int main() {
testCopy();  // ~1000μs
testMove();  // ~1μs
return 0;
}

result:

  • Copy: ~1000μs (memory allocation + copy)
  • Movement: ~1μs (copy only pointer)

8. Practice pattern

Pattern 1: Factory function

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <string>
class Database {
std::vector<std::string> data_;
public:
Database(std::vector<std::string> data)
: data_(std::move(data)) {} // move
size_t size() const { return data_.size(); }
};
// factory function
Database createDatabase() {
std::vector<std::string> data;
data.push_back("record1");
data.push_back("record2");
data.push_back("record3");
return Database(std::move(data));// movement
}
int main() {
Database db = createDatabase();// RVO or move
return 0;
}

Pattern 2: Container Optimization

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <string>
int main() {
std::vector<std::string> names;
// ❌ Copy
std::string name1 = "Alice";
names.push_back(name1);// copy
// ✅ Move
std::string name2 = "Bob";
names.push_back(std::move(name2));  // move
// ✅ emplace_back (better)
names.emplace_back("Charlie");// create directly
return 0;
}

Pattern 3: Swap Optimization

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
#include <utility>
class Buffer {
std::vector<char> data_;
public:
Buffer(size_t size) : data_(size) {}
// move based swap
void swap(Buffer& other) noexcept {
data_.swap(other.data_);  // O(1)
}
// or use std::swap
friend void swap(Buffer& a, Buffer& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
}
};
int main() {
Buffer b1(1000), b2(2000);
swap(b1, b2);// fast travel
return 0;
}

9. Practical example: Resource Manager

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
#include <vector>
#include <memory>
#include <string>
class ResourceManager {
std::vector<std::unique_ptr<std::string>> resources_;
public:
// add resources
void add(std::unique_ptr<std::string> resource) {
resources_.push_back(std::move(resource));  // move
}
// Get resources
std::unique_ptr<std::string> take(size_t index) {
if (index >= resources_.size()) return nullptr;
auto resource = std::move(resources_[index]);// move
resources_.erase(resources_.begin() + index);
return resource;
}
// Number of resources
size_t count() const {
return resources_.size();
}
// Resource output
void print() const {
std::cout << "Resources (" << resources_.size() << "):" << std::endl;
for (size_t i = 0; i < resources_.size(); ++i) {
if (resources_[i]) {
std::cout << "  [" << i << "]: " << *resources_[i] << std::endl;
} else {
std::cout << "  [" << i << "]: (moved)" << std::endl;
}
}
}
};
int main() {
ResourceManager mgr;
// add resources
mgr.add(std::make_unique<std::string>("Resource 1"));
mgr.add(std::make_unique<std::string>("Resource 2"));
mgr.add(std::make_unique<std::string>("Resource 3"));
mgr.print();
// Get resources
auto r = mgr.take(1);
std::cout << "\nTook resource: " << *r << std::endl;
std::cout << "\nRemaining:" << std::endl;
mgr.print();
return 0;
}

output of power: 아래 코드는 code를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

Resources (3):
[0]: Resource 1
[1]: Resource 2
[2]: Resource 3
Took resource: Resource 2
Remaining:
Resources (2):
[0]: Resource 1
[1]: Resource 3

organize

Key takeaways

  1. Move Semantics: Move resources without copying them
  2. rvalue reference: Binding temporary objects with T&&
  3. std::move: cast to rvalue (not actual move)
  4. noexcept: Required for move constructor/assignment
  5. Rule of Five: Destructor, copy, and move all implemented

Copy vs Move

FeaturesCopyGo
CostHigh (memory allocation + copy)Low (pointer only)
OriginalMaintenanceinvalidation
GrammarT a = b;T a = std::move(b);
Generatorcopy constructormove constructor
College Admissioncopy assignmenttransfer admission
UseRequires maintaining originalNo original required

Practical tips

Principle of use:

  • When an object is no longer used
  • When returning from a function (when RVO does not work)
  • When inserting a container
  • When transferring ownership (unique_ptr) Performance:
  • Effective for dynamic memory objects
  • Basic type has no effect
  • RVO is faster than move
  • Check with benchmark caution:
  • Do not use objects after moving
  • const objects cannot be moved
  • noexcept required for move constructor
  • Do not use std::move on return

Next steps


Good article to read together (internal link)

Here’s another article related to this topic.

Practical tips

These are tips that can be applied right away in practice.

Debugging tips

  • If you run into a problem, check the compiler warnings first.
  • Reproduce the problem with a simple test case

Performance Tips

  • Don’t optimize without profiling
  • Set measurable indicators first

Code review tips

  • Check in advance for areas that are frequently pointed out in code reviews.
  • Follow your team’s coding conventions

Practical checklist

This is what you need to check when applying this concept in practice.

Before writing code

  • Is this technique the best way to solve the current problem?
  • Can team members understand and maintain this code?
  • Does it meet the performance requirements?

Writing code

  • Have you resolved all compiler warnings?
  • Have you considered edge cases?
  • Is error handling appropriate?

When reviewing code

  • Is the intention of the code clear?
  • Are there enough test cases?
  • Is it documented? Use this checklist to reduce mistakes and improve code quality.

Keywords covered in this article (related search terms)

This article will be helpful if you search for C++, move, rvalue, move semantics, performance, etc.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3