[C++ 筆記] 用 Modern C++ 的方式寫程式吧
Modern C++ 已經改變很多了
歷史包袱的重量
C++ 一直在相容性上不遺餘力,舊的語法能跑,新的語法與關鍵字又一直疊加,最後呈現出來的就是複雜。作為一個長久以來的使用者,看著其他程式語言的崛起與更新,雖然在理性上很能夠理解,但是情感上還是會想掙扎一下,讓我們看看 Modern C++ 改變的那一面,拋棄到舊的習慣,讓我們可以在不失去效能的情況下,用「比較輕鬆」的方式來寫 C++。
Modern C++ 的改變
1. 記憶體管理
告別手動記憶體管理(new/delete)
// 舊式 C/C++ 方式
BigBuffer* buffer = new BigBuffer(size);
// ... 使用 buffer
delete buffer; // 容易忘記,造成記憶體洩漏
// Modern C++ 方式
auto buffer = std::make_unique<BigBuffer>(size);
// 自動管理記憶體,無需手動 delete
使用 Smart Pointer
std::unique_ptr:獨佔所有權std::shared_ptr:共享所有權std::weak_ptr:弱引用,避免循環依賴
2. 用容器取代原生陣列
// 危險的 C 陣列
int arr[100]; // 無邊界檢查,容易越界
// 安全的 Modern C++ 容器
std::array<int, 100> arr; // 編譯時大小固定
std::vector<int> vec; // 動態大小
3. 自動型別推導
C++ 11 開始支援 auto 關鍵字
// 舊的方式,冗長的型別宣告
std::vector<std::string>::iterator it = vec.begin();
// 簡潔的 auto
auto it = vec.begin();
// 結構化綁定(C++17)
auto [key, value] = map_pair;
4. 現代迴圈語法
// 傳統 C 風格迴圈
for (int i = 0; i < arr.size(); ++i) {
process(arr[i]);
}
// Modern C++ 範圍迴圈
for (const auto& elem : arr) {
process(elem);
}
5. Lambda 表達式
// 傳統函式物件
struct Comparator {
bool operator()(int a, int b) const {
return a < b;
}
};
// Modern C++ Lambda
auto compare = [](int a, int b) { return a < b; };
Modern C++ 的其他好東西
使用 std::optional 明確表達「可能為空」的情況
std::optional<int> findValue(const std::map<int, int>& mapping, int key) {
auto it = mapping.find(key);
if (it != mapping.end()) {
return it->second; // 找到了,返回值
}
return std::nullopt; // 沒找到,返回空值
}
// 使用前必須檢查
auto value = findValue(numbers, 42);
if (value.has_value()) {
std::cout << "Found value:" << value.value() << std::endl;
}
像 Python 可以 return None 來表示空值,std::nullopt 也提供了一個表達空值的方式。
傳統的方式只能像是:
bool findResult(const std::vector<int>& mapping, int target, int& result) {
for (const auto& value : mapping) {
if (value == target) {
result = value; // 找到了,設置結果
return true; // 返回成功
}
}
return false; // 沒找到,返回失敗
}
constexpr 可以在編譯時計算
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// 在編譯時就能計算出結果
constexpr int fact5 = factorial(5); // 編譯時計算為 120
用 using 簡化程式
using(稱為型別別名,Type Alias)和 typedef 的功能基本上是一樣的:它們都是為現有的型別建立一個「別名」,而不是建立新的型別。
using:using [新名字] = [舊型別];typedef:typedef [舊型別] [新名字];// 比較不直覺
using 有一個 typedef 做不到的是模板別名(Template Alias)。
例如
template <typename T> using Handler = std::function<void(const T&)>;
Handler<int> intHandler;
用 using 來簡化 std::function 的例子。例如說我們的參數是一個 callback function
void setOnProgress(std::function<void(double, size_t)> callback) {
progressCb = callback;
}
// 用 using 簡化
using ProgressCallback = std::function<void(double percentage, size_t bytesSent)>;
void setOnProgress(ProgressCallback callback) {
progressCb = callback;
}
表達力
Modern C++ 讓程式碼更接近自然語言:
auto result = numbers
| std::views::filter([](int x) { return x % 2 == 0; }) // 挑偶數
| std::views::transform([](int x) { return x * x; }) // 轉平方
| std::ranges::to<std::vector>(); // 直接收進 vector
C++23 加入了 std::ranges::to
更重要的是不用再寫一堆 Iterator。
效能
Modern C++ 的效能提升,精確來說是實踐了 「零成本抽象(Zero-overhead abstraction)」。
constexpr:這真的很好用。能把計算直接塞進編譯階段,執行時完全不用花 CPU 時間。到了 C++20 更有consteval強制編譯期計算。- 移動語意 (Move Semantics):這是 C++ 的續命符。它解決了以前回傳大型物件時,為了怕拷貝太慢得傳指標或寫得扭扭捏捏的問題。現在直接
return沒負擔。
試試看吧,漸進式升級
一開始先試著用 std::array 或 std::vector 來取代傳統的 array。
然後用 smart pointer 來取代 new/delete。
過程中用 auto 來少打一些字。
用 using 來簡化落落長的型別。
用 Lambda 來簡化 callback(不用在遠遠的地方定義 function)。
試試看,當冗長的 code 變得清爽,思路也比較不容易打結。
最後
雖然 C++ 還是有很多可以改進的地方,例如 package 的使用跟 Python 相比就麻煩很多。 但還是有在改變,試試看吧。