小山的 C++ 快速上手筆記
本書是為了讓不熟悉 C++ 的人,在有接觸過其他 OOP 程式語言(例如 Java)的前提下快速上手所做的筆記。
- 本書網址: https://www.slmt.tw/cpp-quick-guide/
- 本書 Repository: https://github.com/SLMT/cpp-quick-guide
先備知識
預期閱讀此筆記前,假定讀者已經:
- 學習過 C 語言(或類似語法的語言)
- 熟悉其他物件導向 (OOP) 程式語言 (e.g., Java, JavaScript, Golang...)
內容哪裡來的?
這本筆記的內容主要是我從以下網站中整理出來的重點回顧:
- TutorialsPoint C++: https://www.tutorialspoint.com/cplusplus/index.htm
- Microsoft Learn - C++ 文件: https://learn.microsoft.com/zh-tw/cpp/cpp/?view=msvc-170
- GeeksforGeeks - The C++ Standard Template Library (STL): https://www.geeksforgeeks.org/the-c-standard-template-library-stl/
- Modern C++ Tutorial: C++ 11/14/17/20 On the Fly: https://changkun.de/modern-cpp/en-us/00-preface/
C++ 基礎知識加強
加強一些 C++ 與其他語言不同之處。
特別資料型別
bool
: boolean 型別wchar_t
: wide characters (16 bits) 字元,用來補足原本的char
型別大小不足的問題,以支援 Unicode- 固定大小整數型別,避免了傳統 C 語言的
int
等型別大小不固定的問題,例如:int64_t
: 64-bit 整數uint32_t
: 32-bit 無號整數- 完整清單: https://en.cppreference.com/w/cpp/types/integer
取得型別的 Max & Min
- 方法一: 使用內建常數
INT32_MAX
(需要引入stdint.h
)
- 方法二:
std::numeric_limits<T>::min()
T
可以換成任意內建型別
變數宣告修飾子
- Storage Class
auto
: 跟代表自動推導的auto
不同,限制變數只能被宣告的 scope 內的程式碼存取,預設情況下 local variable 前都會自動加上auto
- Example:
auto int a = 1
- Example:
register
: 提示 compiler 將變數存在 CPU register (C++11 之後 deprecated)static
:- 用在 local variable 會讓這個 local variable 的值與記憶體空間一直保留到程式結束
- 用在 global variable 會限制該 variable 的 scope 在所在的 file 中
- 用在 class member 會讓該 member shared 在所有 object 中
extern
: 代表變數定義在其他檔案內,linking 時才會引入- Example:
extern int num
- 說明:https://learn.microsoft.com/en-us/cpp/cpp/extern-cpp?view=msvc-170
- Example:
mutable
: 只能用在 class member 上,當物件的變數被指定為const
時,該 class member 仍能夠被外界修改。
- 全域變數宣告時會自動初始化為
NULL
/0
Type Qualifiers
const
: 常數volatile
: 防止存取此變數的動作因為 compiler optimization 的關係被調換位置- 同時也限制 CPU 不能快取此變數在 CPU cache (multi-threading 時很重要)
restrict
: 限制只有此指標變數可以指向該記憶體位置- 屬於 C99 標準,非 C++ 標準
- https://wucodingroad.blogspot.com/2017/05/Cplusplus-restrict.html
Reference (參考)
Reference 是 C++ 引入給變數設定「別名」的功能,跟 pointer 關鍵差異如下:
- Null 值:Pointer 允許
NULL
,reference 不行 - 修改指向位置:Pointer 的變數可以途中修改成指向其他記憶體位置,reference 不行
- 初始化:Reference 初始化時一定要設定指向的變數,pointer 沒有限制
宣告方式:
// declare simple variables
int i;
double d;
// declare reference variables
int& r = i;
double& s = d;
Reference 可以從 function 被 return:
double& setValues( int i ) {
return vals[i]; // return a reference to the ith element
}
但要注意不能 return local variable 的 reference
Arrays
-
宣告方式:
double a[10];
-
多維陣列:
int threedim[5][10][4];
-
初始化:
double balance[] = {1000.0, 2.0, 3.4, 17.0, 50.0};
-
初始化二維陣列:
int a[3][4] = { {0, 1, 2, 3} , /* initializers for row indexed by 0 */ {4, 5, 6, 7} , /* initializers for row indexed by 1 */ {8, 9, 10, 11} /* initializers for row indexed by 2 */ };
-
Array 的變數其實就是 pointer:
double *p; double balance[10]; p = balance;
balance[4]
等價於*(balance + 4)
-
Array 作為 paramater 傳進 function 方法有兩種常見方式:
-
Method 1: 用 pointer
void myFunction(int *param) { // ... }
-
Method 2: 用 unsized array
void myFunction(int param[]) { // ... }
-
-
從 function return array 只有一種做法,就是回傳 pointer:
int * myFunction() { // ... }
C++ String
C++ 有內建 String 型別,所以可以不使用 C-style string (也就是字元陣列)
使用方式:
#include <string>
using std::string;
string s = "aabbcc";
s.size(); // return 6
s.find("bb"); // return 2
s += "123"; // s = aabbcc123
字面值直接使用 string 型別(省掉一個轉換步驟,需要 using namespace std::literals
):
using namespace std::literals;
string s3_2 = "hello world"s;
基本操作:
os << s // 輸出 s
is >> s // 輸入 s
s.empty() // 檢查 s 是否為空
s.size() // s 目前長度
s[n] // 直接取得 s 的第 n 個元素
s.at(n) // 同樣取得第 n 個元素,但會檢查邊界
s1 + s2 // 把 s1 加 s2 取得新的字串
s1.append(s2) // 把 s2 加到 s1 後面,不會產生新字串
s1 += s2 // 等價於 s1.append(s2)
s1 = s2 // 拷貝複製 s2
s1 != s2 // 比較 s1 和 s2 是否相同
<, <=, ==, >=, > // 做大小比較,以字典排序
完整 C++ String 教學: https://tigercosmos.xyz/post/2023/06/c++/std-string-beginner/
Standard Input/Output
C++ 有以下幾個常用的 input/output streams
cin
: 標準輸入cout
: 標準輸出cerr
: 標準錯誤(但沒有 buffer)clog
: 標準錯誤(但有 buffer)
C++ STL
Standard Template Library (STL) 是 C++ 之中最重要的函式庫,包含多項重要的元件:
- Containers: 各種儲存與操作資料的容器
- Algorithms: 各種高效的演算法
- Iterators: 用來依照某些邏輯存取容器內資料的存取器
- Function Objects: 提供將 function 做為參數傳入 function 的方法,可以幫助客製化演算法的行為
- Adapters: 可以用來修改 STL 內其他元件行為的工具
Containers
STL container 清單: https://cplusplus.com/reference/stl/
需要注意的 containers:
-
forward_list
: 只有向前指針的 linked list,比list
(具備前後指針) 使用更少的空間 -
priority_queue
: 用 heap 實作的 queue,最大的東西會在最前面-
一般用法:
int arr[6] = { 10, 2, 4, 8, 6, 9 }; priority_queue<int> pq; // pushing array sequentially one by one for (int i = 0; i < 6; i++) { pq.push(arr[i]); } // printing priority queue cout << "Priority Queue: "; while (!pq.empty()) { cout << pq.top() << ' '; pq.pop(); }
-
修改成最小在最前面:
int arr[6] = { 10, 2, 4, 8, 6, 9 }; priority_queue<int, vector<int>, greater<int>> gquiz(arr, arr + 6);
-
使用自製的 comparator:
class Foo { /* data */ }; class Comparator { public: bool operator() (Foo, Foo) { // Should the first one be in front of the second one? return true; } }; int main() { std::priority_queue<Foo, std::vector<Foo>, Comparator> pq; return 0; }
-
-
set
: sorted set,用紅黑樹實作 -
map
: sorted map,實作與set
相同 -
multiset
: 跟set
一樣是 sorted set,但可以存重複資料 -
multimap
: 跟map
一樣是 sorted map,但可以存重複的 key- 注意用
find(key)
只會拿到一個 key-value pair,如果想要抓出所有重複 key 的 pair,要使用equal_range(key)
equal_range(key)
回傳的是一個 pair,first
是 iterator 的開頭(lower bound, inclusive),second
是 iterator 的結尾 (upper bound, exclusive)
- 注意用
Algorithms
常用 algorithms:
-
sort(begin, end)
: 使用 IntroSort 進行排序-
IntroSort:先嘗試使用 quick sort。 如果發現 partition 差太多,改成用 heap sort。 如果 size 夠小,改成用 insertion sort。
-
基本用法:
int arr[] = {3, 5, 1, 2, 4}; sort(arr, arr + 5); vector<int> vec {3, 5, 1, 2, 4}; sort(vec.begin(), vec.end());
-
-
binary_search(begin, end, value)
: 使用 binary search -
reverse(begin, end)
: 反轉順序
Iterator
可以看 C++ Reference 官網的介紹,簡單明瞭: https://cplusplus.com/reference/iterator/
只要搞懂 Input, Output, Forward, Bidirectional, Random Access 這五種 iterator 的差異,跟他們的從屬關係就好。
Function Objects (Functors)
直接看一例子,這個例子是寫一個 class 幫所有 array 所有的 element 加上想要的數字:
#include <bits/stdc++.h>
using namespace std;
// A Functor
class increment
{
private:
int num;
public:
increment(int n) : num(n) { }
// This operator overloading enables calling
// operator function () on objects of increment
int operator () (int arr_num) const {
return num + arr_num;
}
};
// Driver code
int main()
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr)/sizeof(arr[0]);
int to_add = 5;
transform(arr, arr+n, arr, increment(to_add));
for (int i=0; i<n; i++)
cout << arr[i] << " ";
}
主要是提供了改寫 ()
行為的能力,所以讓一個 object 可以被像 function 一樣呼叫。
其他
Pairs
C++ STL 提供了 pair 可以使用:
// CPP program to illustrate Pair in STL
#include <iostream>
#include <utility>
using namespace std;
// Driver Code
int main()
{
// defining a pair
pair<int, char> PAIR1;
// first part of the pair
PAIR1.first = 100;
// second part of the pair
PAIR1.second = 'G';
cout << PAIR1.first << " ";
cout << PAIR1.second << endl;
return 0;
}
- Pair 可以被比較,會先比第一個再比第二個
C++ 物件導向
Class 的宣告與定義
基本例子:
#include <iostream>
using namespace std;
class Box {
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// Member functions definitions
double Box::getWidth(void) {
return width ;
}
void Box::setWidth( double wid ) {
width = wid;
}
// Main function for the program
int main() {
Box box;
// set box length without member function
box.length = 10.0; // OK: because length is public
cout << "Length of box : " << box.length <<endl;
// set box width without member function
// box.width = 10.0; // Error: because width is private
box.setWidth(10.0); // Use member function to set it.
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
Initialization List
在 constructor 上可以加上特殊的 list 初始化 member data,如下:
C::C(double a, double b, double c): X(a), Y(b), Z(c) {
....
}
Copy Constructor
C++ 規定每一個 class 都要有一個 copy constructor 具備可以複製整個物件的能力,形式如下:
classname (const classname &obj) {
// body of constructor
}
如果沒有的話 C++ 會自動創建一個預設的,做法是會呼叫所有 member variable 的 copy constructor 實現 deep copy。
Friend Functions & Classes
Class 內可以定義 friend function 與 class 如下:
class Box {
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
friend class ClassTwo;
};
這樣寫代表外面的 printWidth
與 ClassTwo
就可以直接存取 Box 的 private variable width
Static Members
C++ 的 class 可以定義 static member,但變數的初始化必須要寫在 class definition 外面:
// foo.h
class foo
{
private:
static int i;
};
// foo.cpp
int foo::i = 0;
其他特性基本跟 Java 的 static 相同。
繼承性
與 Java 的關鍵差異:
- 可以繼承多個 class
- 這點比較像是 Java 的 interface
- 繼承時可以設定 access modifier
-
例如下面的例子將 base class 設定為 public
// Base class class Shape { // ... }; // Derived class class Rectangle: public Shape { // ... };
-
這個用途是可以修改原本 base class 內的 member 存取性質,但只能改得更嚴格,不能放得更鬆
- 設定為 public ⇒ 不修改 base class 的 member
- 設定為 protected ⇒ 原本是 public 的 member 都會變成 protected
- 設定為 private ⇒ 原本是 public 與 protected 的 member 會變成 private
-
Operators Overloading
C++ 的一大特色就是可以修改 operator 對 class 造成的效果
假設有一個 Box
class,希望將 +
的效果改成分別把長寬高相加,那要這樣修改:
class Box {
public:
// ...
// Overload + operator to add two Box objects.
Box operator+(const Box& b) {
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
// ...
}
Function Override & Virtual Functions
在 C++ 中,如果衍伸類別要改寫基底類別的 method,基底類別就必須要將 method 定義為 virtual:
class Shape {
protected:
int width, height;
public:
Shape( int a = 0, int b = 0){
width = a;
height = b;
}
virtual int area() {
cout << "Parent class area :" << width * height << endl;
return width * height;
}
};
class Triangle: public Shape {
public:
Triangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
cout << "Triangle class area :" << (width * height)/2 << endl;
return (width * height / 2);
}
};
這是因為 C++ 在 class 建立時,預設會用 static linkage 綁定 function 的定義,而這個會導致當一個 function 被呼叫時,「只會根據其 variable type 來判斷該呼叫哪一個 function」。也就是說,無論實際上物件是 Shape
還是 Triangle
,只要他是 Shape
的變數,就會呼叫到 Shape
的 area()
。
如果將 function 設定為 virtual,C++ 就會知道該將 function 改用 dynamic linkage 處理,他會根據當下 object 的實際類別來判斷該呼叫哪一個 function (使用 vtable)。
簡單的比較例子:
class Base
{
public:
void Method1 () { std::cout << "Base::Method1" << std::endl; }
virtual void Method2 () { std::cout << "Base::Method2" << std::endl; }
};
class Derived : public Base
{
public:
void Method1 () { std::cout << "Derived::Method1" << std::endl; }
void Method2 () override { std::cout << "Derived::Method2" << std::endl; }
// Note - override is optional; adding it to Method1 would result in an error
};
Base* basePtr = new Derived ();
// Note - constructed as Derived, but pointer stored as Base*
basePtr->Method1 (); // Prints "Base::Method1"
basePtr->Method2 (); // Prints "Derived::Method2"
參考資料: https://stackoverflow.com/questions/2391679/why-do-we-need-virtual-functions-in-c
Abstract Methods & Abstract Class
C++ 並沒有 abstract
關鍵字,但是可以用別的方式做到相同效果。
只要單純宣告但不定義 virtual function,並且加上 = 0
,就稱之為 pure virtual function:
virtual int area() = 0;
這等同於 Java 的 abstract method。
只要 class 中包含至少一個 pure virtual function,那麼該 class 就不能被實體化,因此效果等同於 abstract class。
Class vs. Struct
C++ 的 class 與 struct 其實沒有太大的差別,只是有幾個關鍵差異:
- Class 的 member 預設是 private,struct 是 public
- 繼承的時候也類似,class 預設會是 private 繼承
- Class 可以當作 template 的關鍵字用,struct 不行
C++ 進階語法
Try-Catch
C++ 可以 throw exception,而且可以用 try-catch 抓住。與 Java 最大的不同在於 C++ 的 throw 可以丟出任何種類的資料,catch 只要寫清楚資料的型別就好:
#include <iostream>
using namespace std;
double division(int a, int b) {
if( b == 0 ) {
throw "Division by zero condition!";
}
return (a/b);
}
int main () {
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
} catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
C++ STD 有內建一系列的 exception,可以在下面網頁看到清單:
https://cplusplus.com/reference/exception/exception/?kw=exception
另外官方也鼓勵開發者繼承 exception
class 來製造自己的錯誤種類,其中只需要 override what()
(顯示錯誤資訊的 function) 就好。
Template
Template 就是 Jave 的 generic,只是語法稍微不同,功能也沒那麼強而已。
定義 function template 的範例如下:
template <typename T>
T minimum(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}
定義 class template 的範例如下:
template <class T>
class Stack {
private:
vector<T> elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T top() const; // return top element
bool empty() const { // return true if empty.
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem) {
// append copy of passed element
elems.push_back(elem);
}
注意當 class method 的定義沒有寫在 class 的宣告內時,也要額外宣告 template <class T>
的標籤。
MSFT 官網有針對 template 更加詳盡的介紹: https://learn.microsoft.com/zh-tw/cpp/cpp/templates-cpp?view=msvc-170
Modern C++ (C++11 之後的變化)
棄用語法
- 字串 literal 應該改用
const char *
型別而不是char *
型別宣告 register
的效果被移除(不再具有將變數放入 CPU register 的效果)bool
不可以++
- C 語言風格的轉換方式
(type)
被棄用,應該改用static_cast
、reinterpret_cast
、const_cast
等方式轉換
新增語法
C++11:
-
指定 Null Pointer 應改用
nullptr
而不是NULL
NULL
可能會被解析成int
造成額外問題,例如有兩個 function 分別為foo(int a)
與foo(char* b)
,當我們呼叫foo(NULL)
就不見得會呼叫foo(char* b)
那個。
-
加入
constexpr
:用來表示某一個 expression 或 function 輸出的結果是一個常數-
編譯器會去驗證這點,並且可以以此來優化程式碼
-
範例:
constexpr int fibonacci(const int n) { return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2); }
constexpr int var_constexpr = 1 + 2 + 3;
-
-
提供可以使用 initialization list 初始化任意物件的能力(但需要實作對應的 constructor)
- 範例:
#include <initializer_list> #include <vector> #include <iostream> class MagicFoo { public: std::vector<int> vec; MagicFoo(std::initializer_list<int> list) { for (std::initializer_list<int>::iterator it = list.begin(); it != list.end(); ++it) vec.push_back(*it); } }; int main() { // after C++11 MagicFoo magicFoo = {1, 2, 3, 4, 5}; std::cout << "magicFoo: "; for (std::vector<int>::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl; }
- 範例:
-
加入
auto
:自動型別推導auto i = 5; // i is inferred as int auto arr = new auto(10); // arr is inferred as int *
-
auto
也常用於 template 指定 function return type 時,這樣就不需要額外定義另一個 type 變數:// Old way template<typename R, typename T, typename U> R add(T x, U y) { return x + y; } // After C++11 template<typename T, typename U> auto add(T x, U y){ return x + y; }
-
-
加入
decltype
: 輸出變數是何種型別-
通常搭配
std::is_same<T, U>
使用,檢查是否兩個型別是同一個型別auto x = 1; if (std::is_same<decltype(x), int>::value) std::cout << "type x == int" << std::endl;
-
-
Ranged for loop
std::vector<int> vec = {1, 2, 3, 4}; for (auto element : vec) std::cout << element << std::endl; // read only for (auto &element : vec) { element += 1; // writeable }
- 宣告 ranged for loop 變數的原則:
- 想 copy 數值 => 使用
auto x
- 想直接引用原本的物件,並且可能會修改 => 使用
auto &x
- 想直接引用原本的物件,但不會修改 => 使用
auto const &x
- 想 copy 數值 => 使用
- 宣告 ranged for loop 變數的原則:
-
搭配
template
使用別名:template<typename T, typename U> class MagicType { public: T dark; U magic; }; template<typename T> using TrueDarkMagic = MagicType<std::vector<T>, std::string>; int main() { TrueDarkMagic<bool> you; }
-
Template 變動變數清單:
template<typename... Ts> void magic(Ts... args) { std::cout << sizeof...(args) << std::endl; } magic(); // Print: 0 magic(1); // Print: 1 magic(1, ""); // Print: 2
-
Delegate constructor:允許重複使用同一個 class 內的 constructor
class Base { public: int value1; int value2; Base() { value1 = 1; } Base(int value) : Base() { // delegate Base() constructor value2 = value; } };
-
繼承 constructor:使用
using
關鍵字來使用 base class 的 constructorclass Subclass : public Base { public: using Base::Base; // inheritance constructor };
-
加入
override
: 強制檢查是否複寫不存在的 virtual functionstruct Base { virtual void foo(int); }; struct SubClass: Base { virtual void foo(int) override; // legal virtual void foo(float) override; // illegal, no such virtual function };
-
加入
final
: 阻止繼承或 overridestruct Base { virtual void foo() final; }; struct SubClass1 final: Base { }; // legal struct SubClass2 : SubClass1 { }; // illegal, SubClass1 has final struct SubClass3: Base { void foo(); // illegal, foo has final };
-
拒絕或明確要求預設實作(用於 constructor 或 operator 等等):
class Magic { public: Magic() = default; // explicit let compiler use default constructor Magic& operator=(const Magic&) = delete; // explicit declare refuse constructor Magic(int magic_number); }
-
enum class
- 與
enum
不同處在於:- 不會預設可以轉換為 int
- 多個
enum class
存在相同名稱的 variant 也不會產生名稱衝突
enum class new_enum : unsigned int { value1, value2, value3 = 100, value4 = 100 };
- 與
C++17:
-
可以在 if 條件式宣告變數
if (const auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) { *itr = 4; }
-
可以對 if statement 使用
constexpr
#include <iostream> template<typename T> auto print_type_info(const T& t) { if constexpr (std::is_integral<T>::value) { return t + 1; } else { return t + 0.001; } } int main() { std::cout << print_type_info(5) << std::endl; std::cout << print_type_info(3.14) << std::endl; }
-
宣告變數時可以解構資料,例如下面的
[x, y, z]
:#include <iostream> #include <tuple> std::tuple<int, double, std::string> f() { return std::make_tuple(1, 2.3, "456"); } int main() { auto [x, y, z] = f(); std::cout << x << ", " << y << ", " << z << std::endl; return 0; }
-
Template 變動長度參數展開式:
#include <iostream> template<typename ... T> auto sum(T ... t) { return (t + ...); } int main() { std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl; }
C++20:
-
auto
可以用於 function signature:int add(auto x, auto y) { return x+y; } auto i = 5; // infered as int auto j = 6; // infered as int std::cout << add(i, j) << std::endl;
Lambda Function
Lambda function 指的是為了當下某個特定目的建立的匿名 function,作用通常不是為了減少重複程式碼,而是為了將某種程序做為參數傳遞出去。
語法:
[capture list] (parameter list) mutable(optional) exception attribute -> return type {
// function body
}
其中 [capture list]
代表從 lambda function 外部帶入的變數,注意該變數是在建立 lambda function 的當下就會複製,而不是在呼叫時被複製。
下面是一個 lambda function 的範例,注意建立 lambda function 的當下 value
的數值被 function 給捕獲並且複製一份。 因此就算後續 value
被改成 100
也不會造成影響。
void lambda_value_capture() {
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// At this moment, stored_value == 1, and value == 100.
// Because copy_value has copied when its was created.
}
Capture list 的格式如下:
[]
空清單[name1, name2, ...]
最常見,捕獲一系列變數[&]
捕獲外部參考(copy by reference),讓編譯器自行推斷要捕獲誰[=]
捕獲外部值(copy by value),讓編譯器自行推斷要捕獲誰
C++14 加入可以在 capture list 中寫 expression 的能力:
#include <iostream>
#include <memory> // std::make_unique
#include <utility> // std::move
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
C++14 開始可以在 lambda 使用 auto
:
auto add = [](auto x, auto y) {
return x+y;
};
add(1, 2);
add(1.1, 2.2);
C++11 加入 std::function
,正式明確定義 function 為一種型別:
#include <functional>
#include <iostream>
int foo(int para) {
return para;
}
int main() {
// std::function wraps a function that take int paremeter and returns int value
std::function<int(int)> func = foo;
int important = 10;
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
}
另外也加入了 std::bind
與 std::placeholder
,用以延後傳遞參數的時機:
int foo(int a, int b, int c) {
;
}
int main() {
// bind parameter 1, 2 on function foo,
// and use std::placeholders::_1 as placeholder for the first parameter.
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
// when call bindFoo, we only need one param left
bindFoo(1);
}
std::move
C++11 加入了右值參考 T &&
的概念,用途是可以將一個即將被銷毀的數值轉移到另一個變數。
std::move
提供了將一個左值轉換為右值參考的能力,一旦在 expression 中的右側出現了右值參考,C++ 便不會呼叫 copy constructor,而是改呼叫 move constructor (編譯器預設會實作一個)。
Move constructor 的概念是盡可能地只複製 pointer,而不是整份資料,並同時將舊物件內的 pointer 清除(改成 nullptr
),以避免大量資料被複製的行為發生,這也是 std::move
最常見的用途。
考慮以下例子:
template <class T>
swap(T& a, T& b) {
T tmp(a); // we've made a second copy of a
a = b; // we've made a second copy of b (and discarded a copy of a)
b = tmp; // we've made a second copy of tmp (and discarded a copy of b)
}
在這個例子中,發生了三次資料複製。
我們可以使用 std::move
改寫來避免複製資料:
template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
如果 T
是一個資料結構,例如 vector<int>
,那裡面可能就包含大量資料,用第一版就會浪費大量的資源與時間。
如同 copy constructor,預設 compiler 會幫每一個型別實作一個 move constructor,作法基本上跟 copy constructor 差不多。只差在他不會複製指標指向的資料,而是單純將指標複製一份(做 shallow copy 而非 deep copy)。同時也會修改原本 class 的 member,把所有變數都清空(避免跟 moved 後的物件使用到相同的記憶體空間)。
新增的 Containers
-
std::array
- 威力加強版的 array,提供了一些 container 的額外功能:
std::array<int, 4> arr = {1, 2, 3, 4}; arr.empty(); // check if container is empty arr.size(); // return the size of the container // iterator support for (auto &i : arr) { // ... } // use lambda expression for sort std::sort(arr.begin(), arr.end(), [](int a, int b) { return b < a; }); // illegal, different than C-style array, std::array will not deduce to T* // int *arr_p = arr;
-
std::forward_list
- 只有單向的 linked list
-
unordered_set
/unordered_map
跟其他東西- Hash map 基底的實作,提供 O(1) 插入與查找
-
std::tuple
std::pair
威力加強版,允許任意數量的組合- 常用 function
std::get
: Get the value of a position in the tuplestd::tie
: tuple unpacking
#include <iostream> #include <stdexcept> #include <string> #include <tuple> std::tuple<double, char, std::string> get_student(int id) { switch (id) { case 0: return {3.8, 'A', "Lisa Simpson"}; case 1: return {2.9, 'C', "Milhouse Van Houten"}; case 2: return {1.7, 'D', "Ralph Wiggum"}; case 3: return {0.6, 'F', "Bart Simpson"}; } throw std::invalid_argument("id"); } int main() { const auto student0 = get_student(0); std::cout << "ID: 0, " << "GPA: " << std::get<0>(student0) << ", " << "grade: " << std::get<1>(student0) << ", " << "name: " << std::get<2>(student0) << '\n'; const auto student1 = get_student(1); std::cout << "ID: 1, " << "GPA: " << std::get<double>(student1) << ", " << "grade: " << std::get<char>(student1) << ", " << "name: " << std::get<std::string>(student1) << '\n'; double gpa2; char grade2; std::string name2; std::tie(gpa2, grade2, name2) = get_student(2); std::cout << "ID: 2, " << "GPA: " << gpa2 << ", " << "grade: " << grade2 << ", " << "name: " << name2 << '\n'; // C++17 structured binding: const auto [gpa3, grade3, name3] = get_student(3); std::cout << "ID: 3, " << "GPA: " << gpa3 << ", " << "grade: " << grade3 << ", " << "name: " << name3 << '\n'; }
Smart Pointers
Smart pointer 的加入大幅改進了 C++ 管理記憶體的彈性。以往 C++ 開發者如果沒有正確使用 pointer,則可能會造成該 pointer 指向非法空間 (segmentation fault) 或者 memory leak 等常見問題。如果 C++ 開發者使用 smart pointer,C++ 就能夠自動進行記憶體管理,並在適當的時機釋放該釋放的記憶體。
-
std::shared_ptr
: 使用計數器管理記憶體的 pointer。- 原理:每次複製 pointer 時都會造成內部的計數器加一,pointer 被銷毀時計數器減一,當計數器歸零時(即所有的 pointer 都被銷毀時),就會自動釋放背後物件的記憶體空間。
auto pointer = std::make_shared<int>(10); auto pointer2 = pointer; // reference count+1 auto pointer3 = pointer; // reference count+1 int *p = pointer.get(); // no increase of reference count std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 pointer2.reset(); std::cout << "reset pointer2:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // pointer2 has reset, 0 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
- 原理:每次複製 pointer 時都會造成內部的計數器加一,pointer 被銷毀時計數器減一,當計數器歸零時(即所有的 pointer 都被銷毀時),就會自動釋放背後物件的記憶體空間。
-
std::unique_ptr
: 不能被複製只能 move 的 pointer。-
原理:當每次進行 pointer assignment 的行為時,被限定只能用 move 而不能複製的 pointer。舊的 pointer 在 move 完後會當即失效,無法繼續使用。而當這個唯一的 pointer 被銷毀時,背後物件的記憶體也會跟著被釋放。
#include <iostream> // std::cout #include <memory> int main() { auto pointer = std::make_unique<int>(10); // make_unique, from C++14 // auto pointer2 = pointer; // copy is illegal auto pointer3 = std::move(pointer); // move is OK std::cout << pointer << std::endl; std::cout << pointer3 << std::endl; }
-
-
std::weak_ptr
: 弱指針,針對shared_ptr
設計,建立時不會引起計數器加一。-
shared_ptr
有個常見的問題是,如果有兩個shared_ptr
互相指向對方,那麼就會造成計數器永遠不會變成 0:- 因此這時通常會讓其中一個 pointer 改成用
weak_ptr
,這樣就shared_ptr
就可以正常歸零。
- 因此這時通常會讓其中一個 pointer 改成用
-
用法:直接 assign
shared_ptr
給weak_ptr
即可std::shared_ptr<int> a = std::make_shared<int>(42); std::weak_ptr<int> b = a;
-
C++ Concurrency
std::thread
C++11 引入了標準的 thread 函式庫,用法如下:
#include <iostream>
#include <thread>
int main() {
std::thread t([](){
std::cout << "hello world." << std::endl;
});
t.join();
return 0;
}
Locks
std::mutex
: lock 的基本元件,提供 lock 與 unlock 功能建立 critical section- 不建議直接使用,因為需要記得手動 lock 與 unlock
std::lock_guard
:std::mutex
的 wrapper,創建時自動 lock mutex,解構時自動 unlockstd::unique_lock
:std::mutex
的 wrapper,創建時自動 lock,中途可以選擇 unlock 並再度 lock,解構時會自動 unlock-
建議優先選擇用這個
-
範例:
#include <iostream> #include <mutex> #include <thread> int v = 1; void critical_section(int change_v) { static std::mutex mtx; std::unique_lock<std::mutex> lock(mtx); // do contention operations v = change_v; std::cout << v << std::endl; // release the lock lock.unlock(); // during this period, others are allowed to acquire v // start another group of contention operations // lock again lock.lock(); v += 1; std::cout << v << std::endl; // Unlock when it disappears } int main() { std::thread t1(critical_section, 2), t2(critical_section, 3); t1.join(); t2.join(); return 0; }
-
std::future
一個用來建立 task,等待未來回收結果的 utility 元件。
詳情:https://changkun.de/modern-cpp/en-us/07-thread/#7-3-Future
std::condition_variable
提供 wait()
與 notify_all()
與 notify_one()
的功能,讓 thread 可以進入等待狀態,其他 thread 也可以叫醒等待的 thread
範例:https://changkun.de/modern-cpp/en-us/07-thread/#7-4-Condition-Variable
std::atomic
如果想要保證一些常見操作(例如 ++
)的原子性,除了使用 std::mutex
建立 critical section 之外,也可以使用 std::atomic
。 這樣可以大幅減少要撰寫的程式碼。 下面展示使用 std::atomic
做出 atomic 的 ++
操作的範例:
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> count = {0};
int main() {
std::thread t1([](){
count.fetch_add(1);
});
std::thread t2([](){
count++; // identical to fetch_add
count += 1; // identical to fetch_add
});
t1.join();
t2.join();
std::cout << count << std::endl;
return 0;
}
因為 std::atomic
實際上是一個 template,可以容納任何類型的資料,因此 C++ 有提供額外的 API 檢查該類型資料是否有保障原子性:
#include <atomic>
#include <iostream>
struct A {
float x;
int y;
long long z;
};
int main() {
std::atomic<A> a;
std::cout << std::boolalpha << a.is_lock_free() << std::endl;
return 0;
}