Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
---

<!-- COVERAGE_START -->
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 602/604 docs translated
![English Coverage](https://img.shields.io/badge/en_coverage-98%25-green.svg) 602/614 docs translated
<!-- COVERAGE_END -->

## 这是什么项目
Expand Down
94 changes: 94 additions & 0 deletions code/examples/vol2/move_benchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// move_benchmark.cpp -- 拷贝 vs 移动性能对比(分离构造开销)
// Standard: C++17
// 对应文档:vol2-modern-features/ch00-move-semantics/05-move-in-practice.md
//
// 关键设计:把"构造"这一固定开销单独测出来作为 baseline,再用
// (构造+拷贝) - 构造 和 (构造+移动) - 构造 得到纯粹的拷贝/移动耗时,
// 避免构造开销稀释掉移动操作本身"接近零"的事实。
//
// 注意:绝对耗时是机器相关的,但"纯移动 ≈ 0、纯拷贝 >> 0"的结论稳定。
#include <chrono>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

class BigData {
std::vector<double> payload_;

public:
explicit BigData(std::size_t n) : payload_(n) {
std::iota(payload_.begin(), payload_.end(), 0.0);
}

BigData(const BigData& other) : payload_(other.payload_) {}
BigData(BigData&& other) noexcept = default;
BigData& operator=(const BigData&) = default;
BigData& operator=(BigData&&) noexcept = default;
};

/// @brief 测量函数执行时间的辅助模板
template <typename Func> double measure_ms(Func&& func, int iterations) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
func();
}
auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double, std::milli>(end - start).count();
}

int main() {
constexpr std::size_t kDataSize = 1000000; // 100 万个 double,约 8MB
constexpr int kIterations = 100;

std::cout << "数据大小: " << kDataSize * sizeof(double) / 1024 << " KB\n";
std::cout << "迭代次数: " << kIterations << "\n\n";

// 测试 0:仅构造(baseline)
auto construct_time = measure_ms(
[&]() {
BigData source(kDataSize);
(void)source;
},
kIterations);

std::cout << "仅构造(baseline): " << construct_time << " ms\n";

// 测试 1:构造 + 拷贝
auto copy_time = measure_ms(
[&]() {
BigData source(kDataSize);
BigData copy = source; // 拷贝构造
(void)copy;
},
kIterations);

std::cout << "构造 + 拷贝: " << copy_time << " ms\n";

// 测试 2:构造 + 移动
auto move_time = measure_ms(
[&]() {
BigData source(kDataSize);
BigData moved = std::move(source); // 移动构造
(void)moved;
},
kIterations);

std::cout << "构造 + 移动: " << move_time << " ms\n\n";

// 分离出纯粹的拷贝/移动耗时
double actual_copy = copy_time - construct_time;
double actual_move = move_time - construct_time;

std::cout << "=== 分离后的实际耗时 ===\n";
std::cout << "纯拷贝: " << actual_copy << " ms\n";
std::cout << "纯移动: " << actual_move << " ms\n";

if (actual_move > 0.01) {
std::cout << "加速比: " << actual_copy / actual_move << "x\n";
} else {
std::cout << "移动耗时在测量噪声范围内(接近零)\n";
}

return 0;
}
142 changes: 142 additions & 0 deletions code/examples/vol2/noexcept_sort_vs_realloc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// noexcept_sort_vs_realloc.cpp -- 验证 noexcept 对 std::sort 和 vector 扩容的影响
// Standard: C++17
// 对应文档:vol2-modern-features/ch00-move-semantics/05-move-in-practice.md
//
// 核心结论:
// - std::sort 只用移动,不区分移动操作是否 noexcept(两种类型都是 拷贝=0)
// - vector 扩容通过 move_if_noexcept 选择策略:noexcept 类型用移动,
// 非 noexcept 类型退回拷贝(强异常安全)
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

// 移动操作带 noexcept 的类型
struct NoexceptType {
std::string payload;
int value;

static int copy_count;
static int move_count;

NoexceptType(int v) : payload("data"), value(v) {}
NoexceptType(const NoexceptType& o) : payload(o.payload + "_c"), value(o.value) {
++copy_count;
}
NoexceptType(NoexceptType&& o) noexcept : payload(std::move(o.payload)), value(o.value) {
o.payload = "(moved)";
++move_count;
}
NoexceptType& operator=(NoexceptType&& o) noexcept {
payload = std::move(o.payload);
value = o.value;
o.payload = "(moved)";
++move_count;
return *this;
}
NoexceptType& operator=(const NoexceptType& o) {
payload = o.payload + "_c";
value = o.value;
++copy_count;
return *this;
}
bool operator<(const NoexceptType& rhs) const { return value < rhs.value; }
static void reset() {
copy_count = 0;
move_count = 0;
}
};

// ThrowingType 与 NoexceptType 完全相同,唯一区别是移动操作没有 noexcept
struct ThrowingType {
std::string payload;
int value;

static int copy_count;
static int move_count;

ThrowingType(int v) : payload("data"), value(v) {}
ThrowingType(const ThrowingType& o) : payload(o.payload + "_c"), value(o.value) {
++copy_count;
}
ThrowingType(ThrowingType&& o) // 注意:没有 noexcept
: payload(std::move(o.payload)), value(o.value) {
o.payload = "(moved)";
++move_count;
}
ThrowingType& operator=(ThrowingType&& o) // 注意:没有 noexcept
{
payload = std::move(o.payload);
value = o.value;
o.payload = "(moved)";
++move_count;
return *this;
}
ThrowingType& operator=(const ThrowingType& o) {
payload = o.payload + "_c";
value = o.value;
++copy_count;
return *this;
}
bool operator<(const ThrowingType& rhs) const { return value < rhs.value; }
static void reset() {
copy_count = 0;
move_count = 0;
}
};

int NoexceptType::copy_count = 0;
int NoexceptType::move_count = 0;
int ThrowingType::copy_count = 0;
int ThrowingType::move_count = 0;

int main() {
const int kCount = 5000;

// Test 1: std::sort(noexcept 类型)
{
std::vector<NoexceptType> vec;
vec.reserve(kCount);
for (int i = 0; i < kCount; ++i)
vec.emplace_back(kCount - i);
NoexceptType::reset();
std::sort(vec.begin(), vec.end());
std::cout << "noexcept sort: 拷贝=" << NoexceptType::copy_count
<< " 移动=" << NoexceptType::move_count << "\n";
}

// Test 2: std::sort(非 noexcept 类型)
{
std::vector<ThrowingType> vec;
vec.reserve(kCount);
for (int i = 0; i < kCount; ++i)
vec.emplace_back(kCount - i);
ThrowingType::reset();
std::sort(vec.begin(), vec.end());
std::cout << "非noexcept sort: 拷贝=" << ThrowingType::copy_count
<< " 移动=" << ThrowingType::move_count << "\n";
}

std::cout << "\n";

// Test 3: vector 扩容(noexcept 类型,无 reserve)
{
NoexceptType::reset();
std::vector<NoexceptType> vec;
for (int i = 0; i < 200; ++i)
vec.emplace_back(i);
std::cout << "noexcept 扩容: 拷贝=" << NoexceptType::copy_count
<< " 移动=" << NoexceptType::move_count << "\n";
}

// Test 4: vector 扩容(非 noexcept 类型,无 reserve)
// ThrowingType 的扩容会退回拷贝,因为 move_if_noexcept 不选中它的移动
{
ThrowingType::reset();
std::vector<ThrowingType> vec;
for (int i = 0; i < 200; ++i)
vec.emplace_back(i);
std::cout << "非noexcept扩容: 拷贝=" << ThrowingType::copy_count
<< " 移动=" << ThrowingType::move_count << "\n";
}
}
54 changes: 54 additions & 0 deletions code/examples/vol2/push_back_emplace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// push_back_emplace.cpp -- push_back(拷贝/移动) vs emplace_back(原位构造) 对比
// Standard: C++17
// 对应文档:vol2-modern-features/ch00-move-semantics/05-move-in-practice.md
//
// 核心结论:
// - push_back(lvalue) 触发拷贝构造
// - push_back(std::move(rvalue)) 触发移动构造
// - emplace_back(构造参数) 连移动都省了,直接原位构造
#include <iostream>
#include <string>
#include <vector>

class Heavy {
std::string name_;
std::vector<int> data_;

public:
explicit Heavy(std::string name, std::size_t n) : name_(std::move(name)), data_(n, 42) {
std::cout << " [" << name_ << "] 构造,数据量: " << data_.size() << "\n";
}

Heavy(const Heavy& other) : name_(other.name_ + "_copy"), data_(other.data_) {
std::cout << " [" << name_ << "] 拷贝构造\n";
}

Heavy(Heavy&& other) noexcept : name_(std::move(other.name_)), data_(std::move(other.data_)) {
other.name_ = "(moved-from)";
std::cout << " [" << name_ << "] 移动构造\n";
}

~Heavy() { std::cout << " [" << name_ << "] 析构,数据量: " << data_.size() << "\n"; }

const std::string& name() const { return name_; }
std::size_t data_size() const { return data_.size(); }
};

int main() {
std::vector<Heavy> items;
items.reserve(4);

std::cout << "=== push_back 左值(拷贝)===\n";
Heavy h1("Alpha", 10000);
items.push_back(h1);

std::cout << "\n=== push_back 右值(移动)===\n";
Heavy h2("Beta", 10000);
items.push_back(std::move(h2));

std::cout << "\n=== emplace_back 原位构造 ===\n";
items.emplace_back("Gamma", 10000);

std::cout << "\n=== 程序结束 ===\n";
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Concepts 的三个经典坑,用宏切换复现(默认编译干净,加宏触发对应坑的编译失败)
// 对应文章:02-constraining-templates.md、03-requires-expressions.md
//
// 默认编译(干净,main 演示「用 concept 包装优雅判断负例」的解法):
// g++ -Wall -Wextra -std=c++20 concept_pitfalls.cpp -o cp && ./cp
// 复现三个坑(每个都会编译失败,对照文章看报错):
// g++ -std=c++20 -DDEMO_AMBIGUITY concept_pitfalls.cpp # 坑一:两个互不蕴含的 concept 重载,Duck
// 同时满足 -> 歧义 g++ -std=c++20 -DDEMO_NOSUBSUME concept_pitfalls.cpp # 坑二:C2=C1<T>
// 规范化后原子约束与 C1 相同,不 subsume -> 歧义 g++ -std=c++20 -DDEMO_HARD_ERROR
// concept_pitfalls.cpp # 坑三:对具体类型直接写 requires 表达式 -> 硬错误
#include <iostream>
#include <string>

// 坑一用:两个彼此独立的 concept,谁也不蕴含谁
template <typename T>
concept Swimmable = requires(T t) { t.swim(); };
template <typename T>
concept Flyable = requires(T t) { t.fly(); };
void act(Swimmable auto) {}
void act(Flyable auto) {}

// 坑二用:C2 只是 C1<T> 换名,没有额外原子约束
template <typename T>
concept C1 = requires(T t) { t.a(); };
template <typename T>
concept C2 = C1<T>;
void g(C1 auto) {}
void g(C2 auto) {}

// 坑三的解法:把 requires 表达式包进 concept,求值时 T 是模板参数 -> SFINAE 友好,失败返回 false
// 而非硬错误
template <typename T>
concept HasNope = requires(T t) { t.nope(); };

struct Duck {
void swim() {}
void fly() {}
};
struct X {
void a() {}
};

int main() {
std::cout << std::boolalpha;
// 解法演示:concept 包装后,不存在的成员优雅返回 false
std::cout << "HasNope<std::string>: " << HasNope<std::string> << "\n"; // false,不硬错误

#if DEMO_AMBIGUITY
act(Duck{}); // Duck 同时满足 Swimmable 和 Flyable,两者互不蕴含 -> 编译器选不出 -> 歧义
#elif DEMO_NOSUBSUME
g(X{}); // X 同时满足 C1 和 C2,但 C2 规范化后原子约束= C1,无真包含 -> 歧义
#elif DEMO_HARD_ERROR
static_assert(!requires(std::string s) { s.nope(); }); // 对具体类型 string 直接写 -> 硬错误
#endif
return 0;
}
Loading
Loading