시리즈: C++
cpp
342 줄
· 업데이트 2026-04-03
move_semantics.cpp
C++/Part4_現代CPP/Ch17_移動語意/move_semantics.cpp
// ============================================================
// Ch17 — 移動語意(Move Semantics)
// 編譯:g++ -std=c++17 -Wall -o move_semantics move_semantics.cpp
// ============================================================
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <chrono>
#include <cstring>
#include <algorithm>
// ── 輔助函式 ──
void section(const std::string& title) {
std::cout << "\n========================================\n";
std::cout << " " << title << "\n";
std::cout << "========================================\n";
}
// ============================================================
// 自管理字串類別 — 完整示範 Rule of Five
// ============================================================
class MyString {
public:
// 預設建構子
MyString() : data_(nullptr), size_(0) {
std::cout << " [預設建構] MyString()\n";
}
// 參數化建構子
explicit MyString(const char* str) {
size_ = std::strlen(str);
data_ = new char[size_ + 1];
std::strcpy(data_, str);
std::cout << " [建構] MyString(\"" << data_ << "\")\n";
}
// ── 1. 解構子 ──
~MyString() {
std::cout << " [解構] ~MyString("
<< (data_ ? data_ : "null") << ")\n";
delete[] data_;
}
// ── 2. 複製建構子 ──
MyString(const MyString& other) : size_(other.size_) {
data_ = new char[size_ + 1];
std::strcpy(data_, other.data_);
std::cout << " [複製建構] MyString(\"" << data_ << "\")\n";
}
// ── 3. 複製賦值運算子 ──
MyString& operator=(const MyString& other) {
std::cout << " [複製賦值] operator=(\"" << other.data_ << "\")\n";
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_ + 1];
std::strcpy(data_, other.data_);
}
return *this;
}
// ── 4. 移動建構子 ──
MyString(MyString&& other) noexcept
: data_(other.data_), size_(other.size_) {
std::cout << " [移動建構] MyString(\"" << data_ << "\") ← 偷走資源\n";
other.data_ = nullptr;
other.size_ = 0;
}
// ── 5. 移動賦值運算子 ──
MyString& operator=(MyString&& other) noexcept {
std::cout << " [移動賦值] operator=(\""
<< (other.data_ ? other.data_ : "null") << "\")\n";
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
// 存取
const char* c_str() const { return data_ ? data_ : ""; }
size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
friend std::ostream& operator<<(std::ostream& os, const MyString& s) {
return os << s.c_str();
}
private:
char* data_;
size_t size_;
};
// ── 回傳 MyString 的函式(展示 RVO)──
MyString createGreeting(const char* name) {
MyString result("Hello, ");
// 實際上編譯器可能直接用 NRVO 省略複製和移動
return result;
}
int main() {
std::cout << "===== Ch17:移動語意完整示範 =====\n";
// ──────────────────────────────────────────
// 1. 左值 vs 右值
// ──────────────────────────────────────────
section("1. 左值 vs 右值");
{
int x = 42; // x 是左值,42 是右值
int y = x + 1; // x + 1 是右值,y 是左值
std::cout << "x = " << x << " (左值,可取位址: " << &x << ")\n";
std::cout << "y = " << y << " (左值)\n";
// 字串的左值與右值
std::string s1 = "Hello"; // "Hello" → 臨時 string → s1
std::string s2 = s1; // s1 是左值 → 複製
std::string s3 = std::move(s1); // std::move(s1) 轉為右值 → 移動
std::cout << "s1(移動後)= \"" << s1 << "\"\n";
std::cout << "s2(複製)= \"" << s2 << "\"\n";
std::cout << "s3(移動)= \"" << s3 << "\"\n";
}
// ──────────────────────────────────────────
// 2. 右值參考
// ──────────────────────────────────────────
section("2. 右值參考 (&&)");
{
// 左值參考
int a = 10;
int& lref = a; // 左值參考綁定左值
// int& lref2 = 42; // 錯誤!左值參考不能綁定右值
const int& clref = 42; // const 左值參考可以綁定右值
// 右值參考
int&& rref = 42; // 右值參考綁定右值
// int&& rref2 = a; // 錯誤!右值參考不能綁定左值
int&& rref3 = std::move(a); // std::move 將左值轉為右值
std::cout << "lref = " << lref << "\n";
std::cout << "clref = " << clref << "\n";
std::cout << "rref = " << rref << "\n";
std::cout << "rref3 = " << rref3 << "\n";
// 重要觀念:rref 本身是一個左值!(它有名字、有位址)
// int&& rref4 = rref; // 錯誤!rref 是左值
int&& rref4 = std::move(rref); // 需要 std::move
std::cout << "rref4 = " << rref4
<< "(rref 本身是左值,需要 std::move)\n";
}
// ──────────────────────────────────────────
// 3. MyString 的複製 vs 移動
// ──────────────────────────────────────────
section("3. MyString 複製 vs 移動");
{
std::cout << "--- 建構 ---\n";
MyString s1("C++");
std::cout << "\n--- 複製建構 ---\n";
MyString s2 = s1; // 觸發複製建構子
std::cout << "s1 = \"" << s1 << "\"\n";
std::cout << "s2 = \"" << s2 << "\"\n";
std::cout << "\n--- 移動建構 ---\n";
MyString s3 = std::move(s1); // 觸發移動建構子
std::cout << "s1(移動後)= \"" << s1 << "\" (empty: "
<< (s1.empty() ? "是" : "否") << ")\n";
std::cout << "s3 = \"" << s3 << "\"\n";
std::cout << "\n--- 複製賦值 ---\n";
MyString s4("Old");
s4 = s2; // 觸發複製賦值
std::cout << "s4 = \"" << s4 << "\"\n";
std::cout << "\n--- 移動賦值 ---\n";
MyString s5("Temp");
s5 = std::move(s3); // 觸發移動賦值
std::cout << "s3(移動後)= \"" << s3 << "\"\n";
std::cout << "s5 = \"" << s5 << "\"\n";
std::cout << "\n--- 離開作用域,所有物件解構 ---\n";
}
// ──────────────────────────────────────────
// 4. std::move 的本質
// ──────────────────────────────────────────
section("4. std::move 的本質");
{
std::cout << "std::move 實際上等同於:\n";
std::cout << R"(
template<typename T>
typename std::remove_reference<T>::type&& move(T&& t) {
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
)";
std::cout << "→ 它只是一個 static_cast,不做任何實際移動\n";
std::cout << "→ 真正的移動發生在移動建構子或移動賦值中\n";
// 示範:std::move 對 const 物件無效
const std::string cs = "不可移動";
std::string t = std::move(cs); // 實際上呼叫複製建構子!
std::cout << "\nconst 物件 move 後:\n";
std::cout << " 原始值仍為:\"" << cs << "\"\n";
std::cout << " 新值為:\"" << t << "\"(是複製,不是移動)\n";
}
// ──────────────────────────────────────────
// 5. 移動語意的效能優勢
// ──────────────────────────────────────────
section("5. 效能比較:複製 vs 移動");
{
const int N = 100000;
// 建立一個大向量
std::vector<std::string> source;
source.reserve(N);
for (int i = 0; i < N; i++) {
source.push_back("This is a somewhat long string for testing #"
+ std::to_string(i));
}
// 複製
auto t1 = std::chrono::high_resolution_clock::now();
std::vector<std::string> copied = source; // 複製所有字串
auto t2 = std::chrono::high_resolution_clock::now();
// 移動
auto t3 = std::chrono::high_resolution_clock::now();
std::vector<std::string> moved = std::move(source); // 移動(偷走指標)
auto t4 = std::chrono::high_resolution_clock::now();
auto copy_us = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
auto move_us = std::chrono::duration_cast<std::chrono::microseconds>(t4 - t3).count();
std::cout << "複製 " << N << " 個字串:" << copy_us << " μs\n";
std::cout << "移動 " << N << " 個字串:" << move_us << " μs\n";
std::cout << "移動快了約 " << (copy_us > 0 ? copy_us / std::max(move_us, (long long)1) : 0)
<< " 倍\n";
std::cout << "source 移動後大小:" << source.size() << "\n";
}
// ──────────────────────────────────────────
// 6. Rule of Five 完整範例
// ──────────────────────────────────────────
section("6. Rule of Five 完整範例");
{
std::cout << "MyString 類別實作了全部五個特殊成員函式:\n";
std::cout << " 1. 解構子 ~MyString()\n";
std::cout << " 2. 複製建構子 MyString(const MyString&)\n";
std::cout << " 3. 複製賦值運算子 operator=(const MyString&)\n";
std::cout << " 4. 移動建構子 MyString(MyString&&) noexcept\n";
std::cout << " 5. 移動賦值運算子 operator=(MyString&&) noexcept\n\n";
std::cout << "--- 展示所有操作 ---\n";
MyString a("Alpha");
MyString b(a); // 複製建構
MyString c(std::move(a)); // 移動建構
MyString d("Beta");
d = b; // 複製賦值
MyString e("Gamma");
e = std::move(d); // 移動賦值
std::cout << "\n--- 結束,解構所有物件 ---\n";
}
// ──────────────────────────────────────────
// 7. 移動語意與容器
// ──────────────────────────────────────────
section("7. 移動語意與容器");
{
std::vector<MyString> vec;
std::cout << "--- push_back 臨時物件(移動) ---\n";
vec.push_back(MyString("Temp")); // 臨時物件 → 移動
std::cout << "\n--- push_back 具名物件(複製) ---\n";
MyString named("Named");
vec.push_back(named); // 具名物件 → 複製
std::cout << "\n--- push_back + std::move(移動) ---\n";
MyString movable("Movable");
vec.push_back(std::move(movable)); // 強制移動
std::cout << "\nmovable 移動後:\"" << movable << "\"\n";
std::cout << "\n容器內容:\n";
for (size_t i = 0; i < vec.size(); i++) {
std::cout << " [" << i << "] = \"" << vec[i] << "\"\n";
}
std::cout << "\n--- 容器銷毀 ---\n";
}
// ──────────────────────────────────────────
// 8. RVO / NRVO
// ──────────────────────────────────────────
section("8. RVO / NRVO");
{
std::cout << "呼叫 createGreeting:\n";
MyString greeting = createGreeting("World");
std::cout << "greeting = \"" << greeting << "\"\n";
std::cout << "(觀察:可能只有一次建構,沒有複製或移動 → NRVO 生效)\n\n";
std::cout << "重要提醒:\n";
std::cout << " ✓ return local_var; — 允許 NRVO\n";
std::cout << " ✗ return std::move(local_var); — 阻止 NRVO!\n";
}
// ──────────────────────────────────────────
// 9. 被移動後的物件狀態
// ──────────────────────────────────────────
section("9. 被移動後的物件");
{
std::string s = "Important Data";
std::string t = std::move(s);
std::cout << "移動後的 s:\"" << s << "\"\n";
std::cout << "s.size():" << s.size() << "\n";
std::cout << "s.empty():" << (s.empty() ? "是" : "否") << "\n";
// 安全操作:重新賦值
s = "重新賦值是安全的";
std::cout << "重新賦值後:\"" << s << "\"\n";
std::cout << "\n移動後可以做的事:\n";
std::cout << " ✓ 銷毀(解構子)\n";
std::cout << " ✓ 重新賦值\n";
std::cout << " ✓ 呼叫不依賴前置條件的操作(如 size(), empty())\n";
std::cout << " ✗ 假設有特定內容(未定義的狀態)\n";
}
std::cout << "\n===== 移動語意示範結束 =====\n";
return 0;
}
관련 글
C++
c
업데이트 2026-07-21
deviceAlpha.h
deviceAlpha.h — c source code from the C++ learning materials (C++/Mavis_Homework/FinalProject/deviceAlpha.h).
글 읽기 →
C++
c
업데이트 2026-07-21
finalproject.c
finalproject.c — c source code from the C++ learning materials (C++/Mavis_Homework/FinalProject/finalproject.c).
글 읽기 →
C++
cpp
업데이트 2026-07-21
finalproject.cpp
finalproject.cpp — cpp source code from the C++ learning materials (C++/Mavis_Homework/FinalProject/finalproject.cpp).
글 읽기 →
C++
c
업데이트 2026-07-21
deviceAlpha.h
deviceAlpha.h — c source code from the C++ learning materials (C++/Mavis_Homework/Lab8/deviceAlpha.h).
글 읽기 →
C++
c
업데이트 2026-07-21
lab8.c
lab8.c — c source code from the C++ learning materials (C++/Mavis_Homework/Lab8/lab8.c).
글 읽기 →
C++
cpp
업데이트 2026-07-21
lab8.cpp
lab8.cpp — cpp source code from the C++ learning materials (C++/Mavis_Homework/Lab8/lab8.cpp).
글 읽기 →