---
# System prepended metadata

title: 物件導向程式設計：補充教材
tags: [Gamelab]

---

# 物件導向程式設計：補充教材

本文件補充一些 OOP 課程可能沒提及，或是不夠深入的教學內容

## Smart Pointer

> https://en.cppreference.com/w/cpp/header/memory

Smart Pointer（智慧指標）是 C++11 引入的 RAII 工具，用來自動管理動態記憶體的生命週期。相較於傳統裸指標，智慧指標會在物件離開作用域時自動釋放資源，避免記憶體洩漏與懸垂指標。常見類型包括 std::unique_ptr（唯一所有權）、std::shared_ptr（共享所有權）與 std::weak_ptr（非擁有式引用），能安全、彈性地處理各種資源擁有關係。


RAII（Resource Acquisition Is Initialization）資源獲取即初始化

> 資源的有效期與持有資源的對象的生命期嚴格繫結，即由對象的建構函式完成資源的分配（取得），同時由解構函式完成資源的釋放。在這種要求下，只要對象能正確地解構，就不會出現資源洩漏問題。

```cpp
#include <cstdio>

class FileGuard {
public:
    FileGuard(const char* filename, const char* mode) {
        file = std::fopen(filename, mode);
        if (!file) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~FileGuard() {
        if (file) {
            std::fclose(file);
        }
    }

    FILE* get() const { return file; }

private:
    FILE* file;
};

int main() {
    try {
        FileGuard fg("example.txt", "w");
        std::fprintf(fg.get(), "Hello RAII!\n");
        // 自動 fclose()，即使發生例外也保證釋放
    } catch (const std::exception& e) {
        std::fprintf(stderr, "Error: %s\n", e.what());
    }
}
```

那接下來，我們使用 `Example`，用以觀察物件的建立與解構行為：

```cpp
class Example {
public:
  int data;
  Example(int _data) : data(_data) {}
  ~Example() { std::cout << "Destructure this->data: " << data << '\n'; }
};
```

透過解構函式的輸出，可以清楚觀察每個物件被銷毀的時機。

```cpp
std::cout << "Scope 1:" << '\n';
{
  // scope 1, scalar-type
  int *ptr1 = new int(100);
  std::shared_ptr<int> ptr2(new int(200));
  // if you want declare array
  std::shared_ptr<int[]> ptr3(new int[100]);
 
  std::cout << '\t' << "Ptr1 = " << *ptr1 << '\n';
  std::cout << '\t' << "Ptr2 = " << *ptr2 << '\n';
}
```
```
Scope 1:
	Ptr1 = 100
	Ptr2 = 200
```

- `ptr1` 使用裸指標管理記憶體，需手動釋放，存在記憶體洩漏風險。
- `ptr2` 是安全的 `shared_ptr`，會在離開作用域時自動釋放。
- `ptr3` 表示陣列版本的 `shared_ptr`，需使用 `shared_ptr<T[]>` 的語法。

```cpp
  std::cout << "Scope 2:" << '\n';
  {
    // scope 2, Object Type
    Example *ptr1 = new Example(300);
    auto ptr2 = std::make_shared<Example>(400);
    auto ptr3 = ptr2;
    std::cout << '\t' << "Ptr1->data = " << ptr1->data << '\n';
    std::cout << '\t' << "Ptr2->data = " << ptr2->data << '\n';
    std::cout << '\t' << "Ptr3->data = " << ptr3->data << '\n';
 
    // They use same reference.
    std::cout << '\t' << "Ptr2 reference conunt = " 
              << ptr2.use_count() << '\n';
    std::cout << '\t' << "Ptr3 reference conunt = " 
              << ptr3.use_count() << '\n';
  }
```
```
Scope 2:
	Ptr1->data = 300
	Ptr2->data = 400
	Ptr3->data = 400
	Ptr2 reference conunt = 2
	Ptr3 reference conunt = 2
```
- `ptr2` 與 `ptr3` 共用資源，其參考計數為 2。
- `shared_ptr` 透過內部控制區塊追蹤引用數，最後一個離開作用域的指標會觸發 `delete`。

```cpp
  std::cout << "Scope 3:" << '\n';
  {
    // scope 3, Unique Pointer
    auto uptr = std::make_unique<Example>(500);
    // NOT Allowed, because unique pointer only allow one owner
    // auto uptr2 = uptr;
    std::cout << '\t' << "uptr->data = " << uptr->data << '\n';
  }
```
```
Scope 3:
	uptr->data = 500
Destructure this->data: 500
```
- `unique_ptr` 僅允許單一所有者，不能複製。
- 進出 scope 時會自動釋放所管理的資源。

```cpp
  std::cout << "Scope 4:" << '\n';
  {
    // scope 4, Move unique Pointer
    auto uptr = std::make_unique<Example>(500);
 
    // Allowed, Use std::move to explicitly relinquish
    // ownership of the object.
    auto uptr2 = std::move(uptr);
 
    /**
     * The following behavior is undefined because uptr has already been
     * transferred
     */
    // std::cout << "uptr->data = " << uptr->data << '\n';
    std::cout << '\t' << "uptr->data = " << uptr2->data << '\n';
  }
}
```
```
Scope 4:
	uptr->data = 500
Destructure this->data: 500
```

- 使用 `std::move()` 可將擁有權從一個 `unique_ptr` 轉移至另一個。
- 移動後原指標為空（null），再訪問會造成未定義行為。

```cpp
  auto uptr1 = std::make_unique<Example>(600);
  auto f = [m = std::move(uptr1)]() {
    std::cout << "At Lambda, m->data = " << m->data << '\n';
  };
  f();
  // NOT Allow, uptr1 move to lambda capture
  // std::cout << "uptr->data = " << uptr1->data << '\n';
```
```
At Lambda, m->data = 600
Destructure this->data: 600
```
- 此為 C++14 起的初始化捕捉語法。
- 透過 `std::move()` 可將 `unique_ptr` 移入 lambda 內部，並安全釋放於 lambda 結束時。


C++ 的 Smart Pointer 提供了自動化的資源管理機制，透過不同型別的智慧指標，可以根據所有權與用途選擇最適合的管理策略。

| 智慧指標類型 | 所有權模式   | 可複製? | 釋放時機 |
| ---------------------- | ------- | --------------- | -------------- |
| `std::unique_ptr<T>`   | 單一所有者   | 不可複製，可移動 | 離開 scope 時自動刪除 |
| `std::shared_ptr<T>`   | 多個共用者   | 可複製 | 參考計數為 0 時自動釋放  |
| `std::weak_ptr<T>`     | 非擁有式觀察者 | 可複製| 不控制資源生命週期|


| 建立方式                           | 說明                                     |
| ------------------------------ | -------------------------------------- |
| `std::make_unique<T>(args...)` | 建立並初始化 `unique_ptr`，具最佳效能與安全性（C++14 起） |
| `std::make_shared<T>(args...)` | 建立共享控制區與物件，減少配置次數 |
| `std::shared_ptr<T>(new T)`    | 可行但不建議：兩次配置，易混淆所有權 |


>[!Note]
> `make_shared<T[]>` 是不支援的語法
> 這是因為 make_shared 需要知道陣列長度來配置空間，但 `T[]` 是不完整型別
> ```cpp
> std::make_shared<int[]>(100); // Compile Failed
> std::shared_ptr<int[]> ptr(new int[100]); // Allow
> ```

## lambda & Closure (閉包)

> https://en.cppreference.com/w/cpp/language/lambda

Lambda 是 C++11 引入的一種匿名函式語法，用來就地定義一個可呼叫的函式物件，語法簡潔、可捕捉周圍變數。

```cpp
[capture](parameters) -> return_type {
    body
}
```

| 語法單元 | 意義 |
| - | - |
| `[capture]`	  | 指定要捕捉哪些外部變數（by value 或 by reference）|
| `(parameters)`  | 和一般函式一樣的參數清單 |
| `->return_type` | 可選，指定回傳型別（若不指定，使用 auto 推導）
| `{ body }`      | 


```cpp
int a = 10;
auto add = [a](int x) { return a + x; };
std::cout << add(5); // 輸出 15
```

### Capture

透過 capture clause（`[]` 中的語法）將變數「捕捉」進 Lambda 內部。捕捉的方式可以是 值捕捉（by value） 或 參考捕捉（by reference）。

| Capture   | 行為                      |
| --------- | ----------------------- |
| `[x]`     | **值捕捉**變數 `x`（複製一份到內部）  |
| `[&x]`    | **參考捕捉**變數 `x`（引用原本的變數） |
| `[=]`     | 對所有使用到的變數 **值捕捉**       |
| `[&]`     | 對所有使用到的變數 **參考捕捉**      |
| `[=, &x]` | 預設值捕捉，例外對 `x` 使用參考捕捉    |
| `[&, x]`  | 預設參考捕捉，例外對 `x` 使用值捕捉    |

```cpp
int a = 10;
auto f = [a]() { std::cout << a << "\n"; };
a = 20;
f(); // 10
```

```cpp
int a = 10;
auto f = [&a]() { std::cout << a << "\n"; };
a = 20;
f(); // 20, type of a is reference 
```

```cpp
class MyClass {
public:
    int x = 42;
    void print() {
        // capture `this` pointer
        auto f = [this]() { std::cout << x << "\n"; };
        f(); // 42
    }
};
```

```cpp
int y = 10;
// initial capture value
auto f = [z = y + 1]() {
    std::cout << z << "\n";
};
f(); 
```

> https://en.wikipedia.org/wiki/Closure_(computer_programming)

閉包則是從Lambda 延伸出來的一個概念：

```
閉包是指一個函式與其捕捉到的外部環境綁定所形成的物件。
在 C++ 中，lambda 表達式建立的實體就是閉包物件。
```

以下是一個經典的 closure（閉包）示範：

```cpp
#include <iostream>
#include <functional>

void invoke(std::function<void()> f){
    // f 的定義存在於 `invoke` 的參數中
    // 此時 a 應該不可見，但仍然可以正確運作
    f(); // 30
}


int main() {
    int a = 30;
    auto f = [&a](){
        std::cout << a << '\n';
    };
    
    invoke(f);
}
```

另一個經典的 closure（閉包）：

```cpp=
#include <iostream>
#include <functional>

std::function<int()> closure(){
    static int data = 12345;
    return [&data](){
        return data;
    };
}

int main() {
    auto f = closure();
    std::cout << f(); //12345
}

```

在本範例中，`data` 在函式內部具有靜態儲存期（static）。我們透過一個 Lambda 捕捉該值，並且回傳。在 `main` 中，調用了 `closure`，因此取得了 `data` 的內容。

對於 `main` 來說， `closure` 內部宣告的變數是不可見的，但是由於 `closure` 提供了一組對外存取的函式，使 `main` 可以訪問到變數 `data`。在本例子中，`data` 猶如一個私有成員，`closure` 回傳的 lambda 就像個公開成員方法(method)。

請注意，本範例中由於 `data` 被 static 修飾，使其在函式結束後不會被回收。在腳本語言(如 Python, PHP, JavaScript 等)，變數通常會有參考計數，使GC得以正確回收；但在靜態語言中，若此變數不是 static，當函式結束後就會被回收，導致 UB (**Undefined Behavior**)

## std::bind

> https://en.cppreference.com/w/cpp/utility/functional/bind
> Defined in `<functional>` Header

std::bind 是 C++11 引入的工具，用來產生一個新的函式物件，將一個已有的函式、成員函式或可呼叫物件，**部分綁定（partial binding）** 某些參數，延後剩餘參數的傳遞時機。

```
#include <functional>

auto bound = std::bind(callable, arg1, arg2, ...);
```

範例

```cpp
int add(int a, int b) {
    return a + b;
}

auto add5 = std::bind(add, 5, std::placeholders::_1);

std::cout << add5(10); // 15
```

`add5` 是一個接受參數的函式物件，第一個參數綁定5，第二個參數是未定的(`std::placeholders`)，分別用 `_1` , `_2` ... `_20` 表示

由於 add 接受兩個參數，在綁定後已經確認一個參數，因此 add5 只需要在一個參數即可。

```cpp
#include <iostream>
#include <functional>

double devide(double a, double b) {
    return a / b;
}

int main(){
    using namespace std::placeholders;
    auto div1 = std::bind(devide, _1, _2);
    auto div2 = std::bind(devide, _2, _1);
    std::cout << div1(10. , 20.); // 0.5    
    std::cout << div2(10. , 20.); // 2.0 
}
```

該範例則展示了 `placeholders` 的順序，將會影響函數的方法。

由於不支援型別推導，所以不如 lambda 彈性。實際上幾乎可以被 Lambda 取代，但是 `std::bind` 可以協助觀察 class 的行為：

```cpp
#include <iostream>
#include <functional>

class Point {
public:
    int x, y;

    Point(int x, int y) : x(x), y(y) {}

    // Point::print(Point* this) const
    void print() const {
        std::cout << "(" << x << ", " << y << ")\n";
    }
};

int main() {
    Point p1(3, 4);
    Point p2(40, 50);
    auto bound = std::bind(&Point::print, std::placeholders::_1);
    bound(p1);  // (3, 4)
    bound(p2);  // (40, 50)
}
```

這案例模擬了如何實作類別，成員函式不像自由函式，它需要知道是誰的成員函式，也就是需要一個 this 指標。

在一些 C 的函式庫中，模擬OOP的作法就是提供 `CLASS` 以及 `METHOD` 巨集，把某個物件的指標綁定到Function上，展示了 OOP 是一種「開發的範式，而非程式語言特別的實作」。

## std::function

> https://en.cppreference.com/w/cpp/utility/functional/function
> Defined in `<functional>` Header

```cpp
template<class R, class... Args>
class std::function<R(Args...)>;
```

std::function 是一個泛型的可呼叫物件包裝器，可以儲存任何能像函式一樣呼叫的東西，包括：

- 一般函式（function pointer）
- lambda 表達式（含閉包）
- 仿函式（functor）
- 經 std::bind 處理後的物件

```cpp
#include <functional>
#include <iostream>

void say_hello() {
    std::cout << "Hello!\n";
}

int main() {
    std::function<void()> f = say_hello;
    f(); // Hello!
    
    int a = 100;
    std::function<void()> g = [a]() {
        std::cout << a << '\n';
    };
    g(); // 100
}
```

## Functor 仿函式

前面不斷提到該名詞，其意義是：

```
可以通過 operator() 進行調用的物件
```

有很多不同的說法，比方說 `Functor`, `Callable`, `Function Object`

它是一種物件化的函式，可以封裝狀態、擁有生命周期，並可與 STL、lambda 互補。


```cpp
#include <iostream>

struct Adder {
    int base;

    Adder(int b) : base(b) {}

    int operator()(int x) const {
        return base + x;
    }
};

int main() {
    Adder add5(5); // Functor
    std::cout << add5(3) << '\n';
}
```

| 特性         | 函式指標 | Lambda | 仿函式（Functor） |
| ---------- | ---- | ------ | ------------ |
| 可以封裝狀態     | ☓    | ✓      | ✓            |
| 型別可推導      | ☓    | ✓      | ☓（需完整型別）     |
| 可重複使用與擴充   | ☓    | 部分     | ✓（可繼承）       |
| 編譯期可內聯     | ☓    | ✓      | ✓            |
| 可讀性與簡潔性    | ☓    | ✓      | 中等           |
| 可搭配 STL 使用 | ☓    | ✓      | ✓            |


其實 Lambda 就是仿函式的語法糖

```cpp
auto f = [base = 5](int x) { return base + x; };
```

等價於

```cpp
struct __Lambda {
    int base;
    int operator()(int x) const {
        return base + x;
    }
};
__Lambda f{5};

```

在 https://cppinsights.io/ ，可以嘗試輸入一段包含 Lambda 的語法，其會展開成內聯`ReturnType operator(T arg)` 的類別。

## typeid 與 type_info

> https://en.cppreference.com/w/cpp/language/typeid

在 C++ 中， typeid 與 type_info，這是 *RTTI（Run-Time Type Information，執行期型別資訊）* 的一部分。它們能讓我們在程式執行時獲得物件的實際型別，是分析 lambda、仿函式、std::function 等高階 callable 的有力工具。

`typeid` 是 C++ 的運算子，可以用來查詢某個物件或型別的型別資訊。

```cpp
typeid(expression)
typeid(type)
```

```cpp
#include <iostream>
#include <typeinfo>

int main() {
    int x = 42;
    std::cout << typeid(x).name() << '\n';
    // 輸出內容取決於編譯器實作。
    // 在 GCC/Clang 下，通常是縮寫（如 i 表示 int），可用 c++filt 解碼。
    // MSVC 則是提供相對完整的型別名稱
}
```

`typeid` 將會回傳 `std::type_info` 物件

```cpp
class std::type_info {
public:
    bool operator==(const type_info& rhs) const noexcept;
    const char* name() const noexcept;
    virtual ~type_info();
};
```

| 成員          | 說明                     |
| ----------- | ---------------------- |
| `.name()`   | 回傳型別名稱（編譯器特定格式）        |
| `==` / `!=` | 比較兩個型別是否相同             |

```cpp
#include <iostream>
#include <typeinfo>

void printType(const std::type_info& t) {
    std::cout << "type: " << t.name() << '\n';
}

int main() {
    int a = 5;
    float b = 3.14f;
    printType(typeid(a));
    printType(typeid(b));

    std::cout << std::boolalpha << (typeid(a) == typeid(b)) << '\n'; // false
}
```

之所以提及該運算元，是為了解釋 `std::bind`, `std::function`, Lambda 其實是不同型別的物件：

```cpp
#include <iostream>
#include <functional>
#include <typeinfo>

int add(int a, int b) {
    return a + b;
}

int main() {
    // lambda：會產生一個匿名 closure 類別
    auto lambda = [](int x) { return x + 1; };

    // bind：會產生一個複合型別（bind_t）
    auto bound = std::bind(add, 10, std::placeholders::_1);

    // std::function：型別固定，但內部儲存的是 type-erased callable
    std::function<int(int)> f1 = lambda;
    std::function<int(int)> f2 = bound;

    std::cout << "lambda    : " << typeid(lambda).name() << '\n';
    std::cout << "bind      : " << typeid(bound).name() << '\n';
    std::cout << "function1 : " << typeid(f1).name() << '\n';
    std::cout << "function2 : " << typeid(f2).name() << '\n';
}
```

輸出(使用 clang++ 編譯)

```
lambda    : Z4mainEUliE_
bind      : St5_BindIFPFiiiEiSt12_PlaceholderILi1EEEE
function1 : St8functionIFiiEE
function2 : St8functionIFiiEE
```

| 變數              | 實際型別              | 說明                          |
| --------------- | ----------------- | --------------------------- |
| `lambda`        | 匿名 closure 類別     | 每個 lambda 都會產生獨立型別          |
| `bound`         | `std::_Bind` 模板實例 | bind 是模板生成器                 |
| `std::function` | 固定模板型別            | 型別統一，但內部會做型別抹除（type-erased） |

## auto 與 decltype

auto 與 decltype 則是 C++ 現代型別推導的核心工具。

> auto 告訴編譯器：請根據右側表達式的值自動推導型別。

```cpp
int x = 42;
auto y = x;     // y 的型別是 int
```

請注意，auto 將會把型別的額外資訊消除

```cpp
int x = 42;
int& r = x;

auto a = r;   // int（不是 int&）
decltype(r) b = x; // int&（保留參考）

auto& ar = r; // int&（你加了 & 才會保留）
```

> decltype(expr) 會根據「表達式的語法行為」決定它的型別，但不會執行該表達式。

```cpp
int x = 5;
decltype(x) y = 10; // y 是 int

int& rx = x;
decltype(rx) z = x; // z 是 int&

const int cx = 7;
decltype((cx)) d = x; // d 是 const int&，因為 (cx) 是 lvalue
```

兩者結合：

```cpp
int x = 5;
int& rx = x;

auto a = rx;           // int（值）
decltype(auto) b = rx; // int&（保留參考）

decltype(rx) c = x;    // int&
```

使用 `auto` 推導型別的值，再使用 `decltype` 推導是否為參考。

## Template

由於課程已經學過 template 的使用，本節將會針對一些特殊例子進行說明

```cpp
template<typename T>
class Example {
public:
    using valueType = T;  // 宣告別名 valueType 指向模板參數 T
    valueType data;
};
```

這段程式的含意是：

- 類別 `Example<T>` 宣告了一個名為 valueType 的內部型別

- valueType 是 T 的別名，讓類別使用者或子類別可用更語義化的名稱引用 T

```cpp
Example<int>::valueType x = 42;  // int x
Example<double>::valueType y = 3.14;  // double y
```

## using

using 與 typedef 都可以用來處理 Type Alias：

```cpp
using valueType = T;     // 現代寫法（推薦）
typedef T valueType;     // 舊寫法（等價，但較難讀）
```

| 比較項目   | `using` | `typedef` |
| ------ | ------- | --------- |
| 可讀性    | 高     | 低       |
| 支援模板別名 | 是     | 否       |

```cpp
template<typename T>
using Vec = std::vector<T>;  // Template Alias

Vec<int> v1;   // 相當於 std::vector<int>
Vec<double> v2;
```

建議統一使用 using 就好，因為 using 很單純的就是左邊是型別名稱，右邊是型別：

```cpp
typedef void(funcT*)(int);
using funcT = void(*)(int);
```

## Traits

Traits 是一種技巧，用來在編譯期查詢或定義型別相關資訊。

不同型別做出不同行為的判斷或決策，但不需要在執行期用 if 判斷，而是在編譯期就決定好。

```cpp
template<typename T>
struct Traits {
    using valueType = T;
};
```

你可以透過 Traits<T>::valueType 來取得相關型別資訊，這就是所謂的「型別萃取」。
    
```cpp
template<typename T>
struct is_integer {
    static constexpr bool value = false;
};

template<>
struct is_integer<int> {
    static constexpr bool value = true;
};

template<>
struct is_integer<long> {
    static constexpr bool value = true;
};
```
    
## SFINAE

> https://en.cppreference.com/w/cpp/language/sfinae
> Substitution Failure Is Not An Error(SFINAE)
    
其行為：定義多個模板函式，只讓其中某些函式適用於特定型別。編譯器遇到不適用的，就直接忽略它。


在C++ 中，有一個模板 `std::enable_if`，其可能的實作：
    
```cpp
template<bool Condition, typename T = void>
struct enable_if {};

// 當 Condition = true 時，才會產生 type 成員
template<typename T>
struct enable_if<true, T> {
    using type = T;
};

// 當 Condition = false 時，不產生 type 成員
template<typename T>
struct enable_if<false, T> {
};
```

```cpp
#include <iostream>
#include <type_traits>

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
print(T value) {
    std::cout << "integer：" << value << '\n';
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
print(T value) {
    std::cout << "floating：" << value << '\n';
}
```
    
- print<int> 成功推導 ： is_integral<int>::value == true ，通過
- print<double> ： 第一個版本失敗，但不報錯 ，編譯器選擇第二個版本
    
這就是 SFINAE：替換失敗（不是 integral）但不是錯誤，繼續找其他函式。

| SFINAE 目標     | 範例                                              |
| -------- | ----------------------------------------------- |
| 函式啟用與禁用  | `enable_if` 控制參數或回傳型別                           |
| 類別模板部分特化 | `template<typename T, typename = enable_if...>` |
| 選擇重載版本   | 根據 traits 選擇最合適版本                               |
