iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0

因為我是開始學 DX 後才跟 C++ 熟起來的,所以今天要來說明從其他語言到 C++ 最有可能遇到的難關指標,與其他底層比較會碰到的部分。

指標 Pointer

一般物件或著其他的程式語言沒特別設定的話,你拿到的會是 obj 本身,基本上可以操作的範圍只有 data 這個值

Normal object

MyObject obj;

┌───────────────┐
│ obj           │
│               │
│ MyObject      │
│ data = 10     │
└───────────────┘

而在 C++ 語法裡,當你用 * 標示時,你會拿到的是 MyObject 所在的記憶體位址,並存在 ptr 裡面

Pointer

MyObject* ptr;

┌───────────────┐
│ ptr           │
│ 0x00001000 ───────────────┐
└───────────────┘            │
                             ▼
                    0x00001000
                    ┌───────────────┐
                    │ MyObject      │
                    │ data = 10     │
                    └───────────────┘

因為指標可以直接改值、甚至操作記憶體,是個很容易造成 crash 的寫法。也因為生命週期都是自己處理,一不小心就會造成記憶體洩漏的問題。所以現在都不太建議直接操作指標了,而是使用 unique_ptrshared_ptr 這類 smart pointer,來自己處理物件的生命週期。

智慧指標 Smart pointer

unique_ptr

unique_ptr 表示「只有一個擁有者」的 smart pointer。當 unique_ptr 離開作用域時,會自己自動釋放記憶體。

std::unique_ptr<int> p = std::make_unique<int>(10); //宣告方式

unique_ptr 無法被複製,如果要轉移所有權,要使用 std::move

std::unique_ptr<int> b = a //會報錯

std::unique_ptr<int> b = std::move(a); //要這樣寫才能成功轉移

使用情境

class Mesh
{
public:
	Mesh() {}
};

// Mesh 只會有一個
std::unique_ptr<Mesh> mesh = std::make_unique<Mesh>();

shared_ptr

shared_ptr 表示「多個地方共同擁有同一個物件」。

它的內部會記錄目前有多少個 shared_ptr 正在指向這個物件。當最後一個 shared_ptr 被釋放時,物件才會被刪除。

std::shared_ptr<int> a = std::make_shared<int>(10);
std::shared_prt<int> b = a; // a 跟 b 都指向同一個 int

使用情境

std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>();

//如果 Renderer 和 CollisionSystem 都需要共同使用同一個 Mesh,而且不確定誰先被釋放,就可以考慮 shared_ptr。
Renderer renderer(mesh);
CollisionSystem collision(mesh);

型態轉換

static_cast

static_cast 是用來在編譯期決定轉換是否合法與如何轉換

int a = 10;
float b = static_cast<float>(a); // 把 int 轉成 float

dynamic_cast

dynamic_cast 則用在有繼承關係的類別之間進行安全的型別轉換,會在執行時檢查是否可轉換

class Animal
{
public:
    virtual ~Animal() = default;
};

class Dog : public Animal
{
};

Animal* animal = new Dog();

Dog* dog = dynamic_cast<Dog*>(animal); //嘗試確認 animal 實際上是不是 Dog,如果是,就取得 Dog*

reinterpret_cast

reinterpret_cast 是很危險的型別轉換。它會把某段記憶體「重新解釋」成另一種型別。一般比較上層的開發是見不到它的,但因為我們是在操作 DX,自然會用到此功能。

int value = 100;
char* bytes = reinterpret_cast<char*>(&value); //把 int* 當成 char* 來看,用來讀取它的 byte 內容

位元運算

應該在演算法競賽的時候,偶而會有題目是涉及到位元運算的。實際上在寫 Shader 時,確實偶而會遇到這樣的需求來優化運算的速度。這邊來稍微帶過。

&

bit 的 and,只有兩邊都為同個值的時候才會是該值

Ex:

0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1

|

bit 的 or,只要一邊為 1 結果就為 1

Ex:
0 | 0 = 0
1 | 0 = 1
1 | 1 = 1
0 | 1 = 1

<< 跟 >>

<< 為 bit 往左移動

Ex:

0000 //數字為 0
往左移動 1 bit
0000 //數字為 0

0001 //數字為 1
往左移動 1 bit
0010 //數字為 2

0010 //數字為 2
往左移動 1 bit
0100 //數字為 4

>>為 bit 往右移動,後面都跟上面同概念

這裡最簡單的用法之一,是如果今天試圖做屬性標籤,可以用此來決定其包含哪些標籤。

constexpr int TAG_PLAYER = 1 << 0; // 0001
constexpr int TAG_ENEMY  = 1 << 1; // 0010
constexpr int TAG_FLYING = 1 << 2; // 0100
constexpr int TAG_BOSS   = 1 << 3; // 1000

...

int tags = TAG_ENEMY | TAG_FLYING | TAG_BOSS; //會飛的敵方 BOSS

額外

[[nodiscard]]

接下來會蠻常見的寫法,用法也很單純,可以用來提醒使用者這個回傳值是有意義的,不該被隨意忽略。畢竟在底層操作中每個物件的存在跟被使用狀態的追蹤是很重要的,只要一點小差錯就可能導致整個程式在不明的地方停止運作。

  [[nodiscard]]
  ID3D12Resource* getID3DResourceAddress() const {
      return m_vertexBuffer.Get();
  }
  
  ...
  
  	int main() {
		getID3DResourceAddress(); //叫了但不用編譯器可能會跳警告
	}

參考資料

Smart pointers (Modern C++)


上一篇
Day 3:CMake
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言