C++ Advanced

C++ Template Metaprogramming

  • Youtube Playlist by BitsOfQ

Class Template Argument Deduction (CTAD)

  • Starting C++17, compiler can infer types

std::pair<int, double> p(42, 3.14);  // without CTAD
std::pair p(42, 3.14);               // with CTAD, only legal in C++17 and beyond
  • std::pair 這樣的 simple case 很方便,但情況複雜時程式會變難讀,特別是有 template specialization 時應該避免使用

CTAD and Template Partial Specialization

  • 下面這段 code 編譯不過,因為

    1. Compiler 決定 mapMap<int, bool>

    2. Compiler 抓 partial specialization Map<int, DATA> 來用

    3. Map<int, DATA> 沒有對應的 constructor 於是報錯

#include <iostream>

template<typename KEY, typename DATA>
struct Map {
  Map(KEY a, DATA b) {
    std::cout << "Map" << std::endl;
  }
};

template<typename DATA>
struct Map<int, DATA> {
  Map() {
    std::cout << "Map<int, DATA>" << std::endl;
  }
};

int main() {
  auto map = Map{1, true};
}

std::stringstream

  • std::stringstream 要先 #include <sstream>

  • 下面兩種寫法都不行。Operator << 是用來把 data 插入已存在的 stream

std::stringstream ss = "a" << "b" << "c";
std::stringstream ss;
ss = "a" << "b" << "c";
  • 要這樣寫才對

std::stringstream ss;
ss << "a" << "b" << "c";
ss.str()

Always Pass int By Value

  • 雖然 void foo(const int& a){ ... } 不用複製,reference mechanism 也有 overhead,結果可能還更慢

  • Same goes for double, bool, chat 和 enums

  • 如果一個類型不超過 2 bytes,並且複製它的成本很低,應該 pass by value

STL Containers

容器名稱

標頭檔

底層實作結構

array

array

固定大小陣列

vector

vector

動態陣列(連續記憶體)

deque

deque

雙端陣列(分段連續記憶體)

forward_list

forward_list

linked list

list

list

doubly linked list

set, multiset

set

RB tree

map, multimap

map

RB tree

unordered_set, unordered_multiset

unordered_set

hash table

unordered_map, unordered_multimap

unordered_map

hash table

stack

stack

deque by default

queue

queue

deque by default

priority_queue

queue

heap with vector

容器名稱

插入/新增

刪除

查找

array

O(1)

O(1)

O(1)

vector

平均 O(1)

O(n)

O(n)

deque

O(1)

O(1)

O(n)

forward_list

O(1)

O(1)

O(n)

list

O(1)

O(1)

O(n)

set, multiset

O(log n)

O(log n)

O(log n)

map, multimap

O(log n)

O(log n)

O(log n)

unordered_set, unordered_multiset

平均 O(1)

平均 O(1)

平均 O(1)

unordered_map, unordered_multimap

平均 O(1)

平均 O(1)

平均 O(1)

stack

O(1)(push/pop)

O(1)

不支援查找

queue

O(1)(push/pop)

O(1)

不支援查找

priority_queue

O(log n)(push/pop)

O(log n)

O(n)

Google C++ Style Guide (有錯,例如函式命名規則)

核心原則

  • 為讀者優化:程式碼是寫一次、讀很多次,風格應以「易讀、易維護」為優先

  • 一致性至上:整體風格一致,便於協作與工具自動化處理

  • 避免危險語法:限制使用容易出錯或難以維護的 C++ 特性

  • 考量規模與可擴展性:大型程式碼庫需避免污染全域命名空間、減少依賴

  • 合理使用現代 C++ 特性:鼓勵使用 C++20,但避免使用尚未普及的 C++23 特性

命名規則

類型

命名風格

範例

變數

snake_case

total_count

類別 / 結構

PascalCase

DataProcessor

常數

kPascalCase

kMaxSize

函式

snake_case()

process_data()

巨集

ALL_CAPS

MAX_BUFFER_SIZE

檔案與結構

  • 每個 .cc 檔應有對應的 .h

  • Header 檔需使用 #define guard 或 #pragma once

  • Include 順序:相關 header → C 標準庫 → C++ 標準庫 → 第三方 → 專案內部

類別設計

  • 使用 class 表示有行為的物件,struct 僅用於純資料容器

  • 成員變數使用 private,命名加底線:foo_

  • 建構子只做初始化,複雜邏輯應放在 Init()

  • 禁用不必要的複製與指派操作:使用 = delete

函式設計

  • 函式應短小(建議 <40 行),聚焦單一責任

  • 輸入參數優先,輸出參數其後

  • 優先使用回傳值而非輸出參數

  • 禁用預設參數(除非特殊情況)

其他語法建議

  • 禁用 using namespace

  • 禁用 C++ 例外(throw / try / catch

  • 禁用 RTTI(typeid / dynamic_cast

  • 使用 std::unique_ptr 管理資源,避免裸指標

  • 使用 const 修飾不可變參數與變數

Keeping std::function Alive in Lambda

[ ]:
// crash

#include <functional>

class Function {
public:
    Function(std::function<double(double)> func) : m_func(func) {}

    double operator()(double t) const {
        return m_func(t);
    }
    Function operator+(const Function& other) const {
        return Function([=](double t){ return m_func(t) + other.m_func(t); });
    }
private:
    std::function<double(double)> m_func;
};

Function f1([](double t) { return 1.; }),
         f2([](double t) { return 10.; }),
         f3([](double t) { return 100.; });

Function h = f1 + f2 + f3;

// h(3.5) // crash: temp obj (f1 + f2) is gone
[1]:
// working

#include <functional>
#include <memory>

class Function {
public:
    Function(std::function<double(double)> func) : m_func(func) {}

    double operator()(double t) const {
        return m_func(t);
    }
    Function operator+(const Function& other) const {
        auto shared_this = std::make_shared<Function>(*this);
        auto shared_other = std::make_shared<Function>(other);
        return Function([=](double t){ return (shared_this->m_func)(t) + (shared_other->m_func)(t); });
    }
private:
    std::function<double(double)> m_func;
};

Function f1([](double t) { return 1.; }),
         f2([](double t) { return 10.; }),
         f3([](double t) { return 100.; });

Function h = f1 + f2 + f3;

h(3.5)
[1]:
111.00000