人類發展史,就是不斷挖坑、填坑的程序,
語言發展史也是如此!
任何一門設計合理的語言,給你的限制或提供的什么特性,都不是沒有代價的,
C的指標
指標:pointer
指標的思想起源于匯編,指標思想是編程思想歷史上的重大飛躍,
每一個編程語言都使用指標,C語言將指標完全暴露給了用戶,潘多拉之盒,
使用指標的必要性:資源管理,即地址管理,
思想層:將地址包了一層,
語法層:T *p; *p;
編譯器:包含一個intptr_t型別成員的結構體,
匯編層:暫存器間接尋址MOV,

C語言中只有一種引數傳遞方式:值傳遞,
void f(int p)
void f(int *p)
利用指標交換兩個數字
#include <stdio.h>
void Swap(int *p1,int *p2){
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
int main(){
int a = 10;
int b = 20;
Swap(&a,&b);
printf("%d %d\n",a,b);
return 0;
}
指標的級數
Int *p; int **p; int ***p;
理論上無限級,無限套娃,實際上受編譯器限制,
指標是一扇門,推開門,后面是整個世界,
C++的參考
參考:reference
已存在變數的別名,
使用參考的必要性:資源使用
思想層:受限制的指標,
語法層:T a; T &p=a;
編譯器:給你做了保證,一定是經過初始化的指標
匯編層:和指標一樣,
在匯編層,指標和參考是完全一樣的,
參考是一個語法糖,T a; T &p=a; 等價于 T *const p = &a
int x=0;
00676664 mov dword ptr [x],0
int &a = x;
0067666B lea eax,[x]
0067666E mov dword ptr [a],eax
a = 1;
00676671 mov eax,dword ptr [a]
00676674 mov dword ptr [eax],1
int *p = &x;
0067667A lea eax,[x]
0067667D mov dword ptr [p],eax
*p = 2;
00676680 mov eax,dword ptr [p]
00676683 mov dword ptr [eax],2
int *const p2 = &x;
00676689 lea eax,[x]
0067668C mov dword ptr [p2],eax
*p2 = 3;
0067668F mov eax,dword ptr [p2]
00676692 mov dword ptr [eax],3
參考的情況:
int a = 1;
const int b = 1;
int &ref1 = a;
int &ref2 = 1;//ERROR
const int &ref3 = b;
const int &ref4 = 1;
Q:唯獨int &ref2 = 1;//ERROR?
A:C++的早期這種語法是被允許的,但是在函式呼叫傳引數時,會給程式員帶來誤解,于是后面就禁止了這種語法,
參考規則的特例:const參考
void f(int &i){}
void f(const int &i){}
int main(){
int i = 1;
f(i);//call f(int &i)
f(2);//call f(const int &i)
return 0;
}
void f(int &i){}
//void f(const int &i){}
int main(){
int i = 1;
f(i);//call f(int &i)
f(2);//ERROR
return 0;
}
//void f(int &i){}
void f(const int &i){}
int main(){
int i = 1;
f(i);//call f(const int &i)
f(2);//call f(const int &i)
return 0;
}
C++語言中就有了新的引數傳遞方式:參考傳遞 void f(T &p) ,實質也是傳值,
自定義型別最好用參考傳遞,可以避免不必要的建構式和解構式的呼叫,
內置型別建議用值傳遞,自定義型別建議用參考傳遞,內置型別,值傳遞會比按參考傳遞更高效,
解釋見:這里
利用參考交換兩個數字
#include <iostream>
#include <stdlib.h>
using namespace std;
void swap(int &a, int &b){
int tmp = a;
a = b;
b = tmp;
}
int main(){
int a = 3;
int b = 4;
swap(a, b);
cout << "a=" << a<<" " << "b=" << b << endl;
return 0;
}
參考的級數
只能一級,參考的物件必須是一個已存在的地址,參考變數本身的地址,外界不能訪問,
References are not objects; they do not necessarily occupy storage,
Because references are not objects, there are no arrays of references, no pointers to references, and no references to references,
int& a[3]; // ERROR
int&* p; // ERROR
int& &r; // ERROR
參考和指標疊加
int a; int *p = &a; int *&r = p; //OK
使用參考的場景:
- 給函式傳遞可變引數
- 給函式傳遞大型物件
- 參考函式回傳值;
Q:參考能實作的基本上指標都可以實作,那為什么C++還需要引入參考呢?
A:最初主要是為了支持運算子多載,
c = a + b是可以接受的寫法,而c = &a + &b 就不是很方便而且有歧義了,
寫法上的方便是要第一考慮的,
Q:C++引入了參考,那為什么C++不和Java一樣讓指標對使用者不可見呢?
A:歷史原因,為了兼容C語言,程式員是自由的,
Q:C++為什么選擇&作為參考的識別符號?
A:需要用一個符號告訴編譯器,傳的是參考,&在C語言中是取地址的作用,于是就選擇了它,
Q:this為什么是指標型別,而不是參考型別?
A:歷史原因,this誕生早于參考,某種意義上來講,this應該被設計為參考型別,
Q:Why is "this" not a reference?
A:Because "this" was introduced into C++ (really into C with Classes) before references were added. Also, I chose "this" to follow Simula usage, rather than the (later) Smalltalk use of "self".
Q:拷貝建構式引數一定是參考,不然編譯通不過,為什么?
A:因為在入參的程序中,如果不是參考,會首先進行一次值拷貝;而要實作的就是拷貝構造,就會造成不斷的遞回最后爆炸,
Q:參考是受限制的指標,哪里受限制了?
A:參考變數本身的地址外界不可獲得,當然編譯器是可以的,
Q:參考變數是否占有記憶體空間?
A:參考可以不占用,也可以占有,語法規定對參考變數的操作都是對被參考物件的操作,
struct S {
int a;
int &b;
};
int x = 123;
S s(123,x);
sizeof(S)=?//32位環境等于8
non-const參考的匯編視角

const參考的匯編視角

說明:const參考變數系結沒有地址的物件時,會生成一個臨時變數/匿名物件來中轉,
全域定義
const int& a = 123; 123的匿名物件在堆上
區域定義
void f{
const int& a = 456; 456的匿名物件在堆疊上
}
往下走用*,往上走用&,
C++的第一個坑:兼容了C語言的指標,
C++的構造
3種構造語意:
- 建構式constructor
- 拷貝構造copy constructor
- 拷貝賦值運算子copy assignment operator
建構式S()
出廠設定
拷貝構造S(const S &other)
把A的資料復制給B,B(A);
拷貝賦值運算子S& operator=(const S &other)
先把B的資源釋放,再把A的資料復制給B,B=A;
變數按記憶體分為兩種:
- 不包含指標,trivial type,籃球,
- 包含指標,handle type,風箏和風箏線,
拷貝的分類
- 淺拷貝
參考語意(reference semantics)
缺陷:若寫法不對,可能會發生double free,
Q:為什么編譯器所有的默認的行為都是淺拷貝?
A:深拷貝不一定能實作,指向的物件可能是多型的,也可能是陣列,也可能有回圈參考,所以只能留待成員變數的類來決定怎樣實作復制,
有時候為了防止默認拷貝發生,可以宣告一個私有的拷貝建構式,這種做法比較常見,但不可取, - 深拷貝
值語意(value semantics)
缺陷:出現了額外的構造和析構,性能損失,
深拷貝和淺拷貝的本質區別就是兩個物件的行為屬性是否是獨立變化的,
C++的第二個坑:拷貝建構式
思考:
T為handle type,T A(...),T B(A),A賦值給B,如果A不再使用了,能不能讓B直接接管A的所有資源呢?(移動語意move semantics)
在不破壞現有語法規則情況下,你會如何設計?
- C++03現有語法不支持移動語意,需要新增移動語意,
- 如何標識物件的資源是可以被移動的呢?
- 這種機制必須以一種最低開銷的方式實作,并且對所有的類都有效,
- 設計在編譯層,與運行層面無關,
C++的設計者們注意到,大多數情況下,右值所包含的物件都是可以安全的被移動的,
左值與右值
左值和右值的概念
CPL語言引入了運算式值的型別value categories這種概念:左值和右值,left or right of assignment,
C語言沿用了類似的分類:左值和其他,locator value and other
C++98 沿用了C語言的分類,但是略微調整,引入了新定義:右值rvalue = https://www.cnblogs.com/txtp/archive/2022/01/17/non-lvalue,
C++11 新增了xvalue(an “eXpiring” value),并調整了之前左值和右值的定義,

(i)has identity: it's possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify (obtained directly or indirectly);
(m)can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression.
準則 1:能不能分辨兩個運算式指的是同一個物體,比如我們可以通過比較地址,
準則 2:能不能使用移動語意,比如看看能不能用呼叫移動建構式,
- i&~m:lvalue 左值
- i&m:xvalue 將亡值
- ~i&m:prvalue 純右值
- i:glvalue泛左值
- m:rvalue右值
C++17
分類和 C++11 是一樣的,但是語意上更加明確了,
- glvalues:有自己地址的長壽物件
- prvalues:為了初始化而用的短命物件
- xvalue:資源已經不需要了,而且可以再利用的長壽物件
為了優化這樣一個情況:T(T(T(x)))==>T(x),將prvalues的定義略微調整了下,
具體可以參考Copy elision (復制消除)
左值參考和右值參考
T &Lref; // 左值參考,就是傳統的c++參考
T &&Rref; // 右值參考
Q:為什么使用&&做為右值參考的識別符號?
A:慣性設計,標準委員玩標點符號是真的可以,
規則:
- non-const左值參考只能系結non-const左值
- non-const右值參考只能系結non-const右值
- const左值參考,可以系結任意,
- const右值參考,可以系結non-const右值和const右值,注:這個使用的場景很少很少,
如何判定
namespace test {
template <typename T> struct is_lvalue_reference {
const static bool value = https://www.cnblogs.com/txtp/archive/2022/01/17/false;
};
template struct is_lvalue_reference {
const static bool value = true;
};
template struct is_rvalue_reference {
const static bool value = false;
};
template struct is_rvalue_reference {
const static bool value = true;
};
template struct is_lvalue {
const static bool value = is_lvalue_reference::value && (!is_rvalue_reference::value);
};
template struct is_xvalue {
const static bool value = (!is_lvalue_reference::value) && is_rvalue_reference::value;
};
template struct is_prvalue {
const static bool value = (!is_lvalue_reference::value && !is_rvalue_reference::value);
};
template struct is_rvalue {
const static bool value = (is_xvalue::value || is_prvalue::value);
};
template struct is_glvalue {
const static bool value = (is_xvalue::value || is_lvalue::value);
};
}
struct Foo {};
Foo funRetFoo();
Foo &funRetFooLRef();
Foo &&funRetFooRRef();
TEST(TypeTraits, isRvalue) {
//base type
EXPECT_FALSE(::test::is_lvalue_reference<int>::value);
EXPECT_FALSE(::test::is_rvalue_reference<int>::value);
EXPECT_FALSE(::test::is_lvalue<int>::value);
EXPECT_FALSE(::test::is_xvalue<int>::value);
EXPECT_TRUE(::test::is_prvalue<int>::value);
EXPECT_FALSE(::test::is_glvalue<int>::value);
EXPECT_TRUE(::test::is_rvalue<int>::value);
// return obj
EXPECT_FALSE(::test::is_lvalue_reference<decltype(funRetFoo())>::value);
EXPECT_FALSE(::test::is_rvalue_reference<decltype(funRetFoo())>::value);
EXPECT_FALSE(::test::is_lvalue<decltype(funRetFoo())>::value);
EXPECT_FALSE(::test::is_xvalue<decltype(funRetFoo())>::value);
EXPECT_TRUE(::test::is_prvalue<decltype(funRetFoo())>::value);
EXPECT_FALSE(::test::is_glvalue<decltype(funRetFoo())>::value);
EXPECT_TRUE(::test::is_rvalue<decltype(funRetFoo())>::value);
// return ref obj
EXPECT_TRUE(::test::is_lvalue_reference<decltype(funRetFooLRef())>::value);
EXPECT_FALSE(::test::is_rvalue_reference<decltype(funRetFooLRef())>::value);
EXPECT_TRUE(::test::is_lvalue<decltype(funRetFooLRef())>::value);
EXPECT_FALSE(::test::is_xvalue<decltype(funRetFooLRef())>::value);
EXPECT_FALSE(::test::is_prvalue<decltype(funRetFooLRef())>::value);
EXPECT_TRUE(::test::is_glvalue<decltype(funRetFooLRef())>::value);
EXPECT_FALSE(::test::is_rvalue<decltype(funRetFooLRef())>::value);
// return rref obj
EXPECT_FALSE(::test::is_lvalue_reference<decltype(funRetFooRRef())>::value);
EXPECT_TRUE(::test::is_rvalue_reference<decltype(funRetFooRRef())>::value);
EXPECT_FALSE(::test::is_lvalue<decltype(funRetFooRRef())>::value);
EXPECT_TRUE(::test::is_xvalue<decltype(funRetFooRRef())>::value);
EXPECT_FALSE(::test::is_prvalue<decltype(funRetFooRRef())>::value);
EXPECT_TRUE(::test::is_glvalue<decltype(funRetFooRRef())>::value);
EXPECT_TRUE(::test::is_rvalue<decltype(funRetFooRRef())>::value);
int lvalue;
// 模擬=號左邊
EXPECT_TRUE(::test::is_lvalue_reference<decltype(*&lvalue)>::value);
EXPECT_FALSE(::test::is_rvalue_reference<decltype(*&lvalue)>::value);
EXPECT_TRUE(::test::is_lvalue<decltype(*&lvalue)>::value);
EXPECT_FALSE(::test::is_xvalue<decltype(*&lvalue)>::value);
EXPECT_FALSE(::test::is_prvalue<decltype(*&lvalue)>::value);
EXPECT_TRUE(::test::is_glvalue<decltype(*&lvalue)>::value);
EXPECT_FALSE(::test::is_rvalue<decltype(*&lvalue)>::value);
//operator++()
EXPECT_FALSE(::test::is_lvalue_reference<decltype(lvalue++)>::value);
EXPECT_FALSE(::test::is_rvalue_reference<decltype(lvalue++)>::value);
EXPECT_FALSE(::test::is_lvalue<decltype(lvalue++)>::value);
EXPECT_FALSE(::test::is_xvalue<decltype(lvalue++)>::value);
EXPECT_TRUE(::test::is_prvalue<decltype(lvalue++)>::value);
EXPECT_FALSE(::test::is_glvalue<decltype(lvalue++)>::value);
EXPECT_TRUE(::test::is_rvalue<decltype(lvalue++)>::value);
//operator++(int)
EXPECT_TRUE(::test::is_lvalue_reference<decltype(++lvalue)>::value);
EXPECT_FALSE(::test::is_rvalue_reference<decltype(++lvalue)>::value);
EXPECT_TRUE(::test::is_lvalue<decltype(++lvalue)>::value);
EXPECT_FALSE(::test::is_xvalue<decltype(++lvalue)>::value);
EXPECT_FALSE(::test::is_prvalue<decltype(++lvalue)>::value);
EXPECT_TRUE(::test::is_glvalue<decltype(++lvalue)>::value);
EXPECT_FALSE(::test::is_rvalue<decltype(++lvalue)>::value);
}
記住一點:左值參考直接作用于lvalue,右值參考直接作用于xvalue,
Q:誰直接作用于prvalue呢?
A:只能間接作用:右值參考、const左值參考,生成一個指標型別的匿名變數做中轉,
移動語意
如何讓自定義物件支持移動語意?
- 移動構造move constructorS(S &&other) noexcept
- 移動賦值運算子move assignment operator S& operator=(S &&other) noexcept
note:需要加noexpect,
目的:告訴編譯器:這個移動函式不會拋出例外,可以放心地呼叫,否則,編譯器會呼叫拷貝函式,
C++11后,STL都支持了移動語意,移動語意對基本型別沒有性能提升的作用,
Q:如何將一個變數轉為右值參考?
A:static_cast<T &&>(t)
寫法一:
string s = "abcd";
string s1(static_cast<string &&>(s));
s1(s);
string s2 = static_cast<string &&>(s);
這種寫法麻煩,如何簡寫?模板,
寫法二:
//VS
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
// FUNCTION TEMPLATE move
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
string s = "abcd";
string s1(move(s));
string s2 = move(s);
由此誕生了std::move,在
利用移動交換兩個數字
void swap(T &a1, T &a2){
T tmp(std::move(a1)); // a1 轉為右值,移動建構式呼叫,低成本
a1 = std::move(a2); // a2 轉為右值,移動賦值函式呼叫,低成本
a2 = std::move(tmp); // tmp 轉為右值移動給a2
}
Q:move的引數是&&,為什么傳入左值也是可以的?
A:萬能參考
A=B B的資源給A了,A的資源自己處理掉的,如果B在外面繼續使用,則是未定義行為,
萬能參考
universal reference
概念:使用T&&型別的形參既能系結右值,又能系結左值,
T&&在代碼里并不總是右值參考,
萬能參考一定涉及到型別推導的,沒有型別推導就是右值參考,
具體規則:這篇文章
使用場景有2個:
template<typename T>
void f(T&& param); // param is a universal reference
auto&& var2 = var1; // var2 is a universal reference
參考折疊規則
reference-collapsing rules
引入該規則的原因:C++中禁止reference to reference,為了通過編譯器檢查,
當左值參考和右值參考同時出現在型別定義中時,需要如何處理?
約定:只有&& && = &&,沾上一個&就變左值參考了,
T && && ==>T &&
T && & ==>T &
T & && ==>T &
T & & ==>T &
Q:為什么要這么約定?
A:左值參考有副作用
函式內外引數型別不匹配
template<typename T>
void f(T&& a){
g(a); // 這里的 a 是什么型別?
}
// 版本 1
template<typename T>
void g(T &){ cout << "T&" << endl; }
// 版本 2
template<typename T>
void g(T &&){ cout << "T&&" << endl; }
int num;
f(0);
f(num);
輸出:
T&
T&
a是變數,是左值,所以輸出T&,
但是0是數字,是右值,為什么進去后就成了左值?能不能一致?
Q:一定要一致么?
A:在某些場景不一致會有問題,需要提供一種保證前后語意一致的機制,
Q:怎么才能實作一致呢?
A:還是static_cast,
template<typename T>
void f(T&& a){
g(static_cast<T &&>(a));
//g(static_cast<T>(a)); 這樣寫也可以
}
// 版本 1
template<typename T>
void g(T &){ cout << "T&" << endl; }
// 版本 2
template<typename T>
void g(T &&){ cout << "T&&" << endl; }
int a;
f(0);
f(a);
輸出:
T&&
T&
這種寫法麻煩,如何簡寫?模板,
Forward
轉發:某些函式需要將其中一個或多個實參連同型別不變地轉發給其他函式
//VS
// FUNCTION TEMPLATE forward
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
就可以簡寫:
template<typename T>
void f(T&& a){
g(forward<T>(a));
}
好像也沒啥好處,就是static_cast替換為了forward,
區別:
- static_cast 關鍵字
- std::forward 模板函式,支持函式模板的變長引數,在
頭檔案中,
例子:make_unique
// FUNCTION TEMPLATE make_unique
template <class _Ty, class... _Types, enable_if_t<!is_array_v<_Ty>, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique(_Types&&... _Args) { // make a unique_ptr
return unique_ptr<_Ty>(new _Ty(_STD forward<_Types>(_Args)...));
}
template <class _Ty, enable_if_t<is_array_v<_Ty> && extent_v<_Ty> == 0, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique(size_t _Size) { // make a unique_ptr
using _Elem = remove_extent_t<_Ty>;
return unique_ptr<_Ty>(new _Elem[_Size]());
}
Perfect forward
轉發時,需要保持被轉發實參的所有性質不變:是否const、以及是左值還是右值,
完美轉發 = std::forward + 萬能參考 + 參考折疊
用途:一般作為多引數函式呼叫的中間層,
struct A{
A(int &&n){ cout << "rvalue overload, n=" << n << endl; }
A(int &n){ cout << "lvalue overload, n=" << n << endl; }
A(const int &&n){ cout << "rvalue const overload, n=" << n << endl; }
A(const int &n){ cout << "lvalue const overload, n=" << n << endl; }
};
class B{
public:
template<typename T1, typename T2, typename T3, typename T4>
B(T1 &&t1, T2 &&t2, T3 &&t3, T4 &&t4) :
a1_(std::forward<T1>(t1)),
a2_(std::forward<T2>(t2)),
a3_(std::forward<T3>(t3)),
a4_(std::forward<T4>(t4)) {}
private:
A a1_, a2_, a3_, a4_;
};
int main(){
int i = 1, j = 2, k = 3, m = 4;
int &i_lref = i;
const int &j_clref = j;
int &&k_rref = std::move(k);
const int &&m_crref = std::move(m);
B b1(1, 2, 3, 4);
B b2(i, j, k, m);
B b3(i_lref, j_clref, k_rref, m_crref);
B b4(i_lref, j_clref, std::move(k), static_cast<const int &&>(m));
return 0;
}
直觀的感受:建構式只用寫一個,就可以滿足所有情形,代替了4^4=256種多載形式,
perfect的由來:引數傳遞的七種方案
不支持轉發的場景 EffectiveModernCppChinese/item30.md
匯編視角
第一個視角

右值參考就是一個指向一個匿名變數的指標,右值參考就是給了外界接觸這個匿名變數的機會,
可見初始化一個右值參考其實是開辟了兩塊空間,一塊是右值參考型別那么大的匿名變數,一塊是指向這個匿名變數的指標,
第二個視角

修改右值參考值的程序也分為兩步,取出指標的值,也就是匿名變數的地址,把右值賦值給地址所指的匿名變數,和修改指標的值一樣的,
Q:如何理解“右值參考延長了右值的生命周期”?
A:右值被放到了一個變數里,已經擁有了記憶體,當然就避免了被從暫存器里扔出去就消失的命運,這一塊記憶體的生命周期就和右值參考變數一致了,
最佳實踐
網上找的一個圖:

資源使用語意
老師給大寶一個球,大寶拿去玩,老師說:注意些別玩壞了,
球的狀態:可變:non-const,不可變:const,
意圖的體現,
大寶在玩球,小寶也想玩,怎么辦?
1. 小寶加入他,一起玩,
2. 大寶不給,小寶買了一個一模一樣的球,各玩各的,
3. 大寶說:我不玩了,給你玩吧,小寶接著玩,
需要三個語意來表達(實際上也足夠了):
1. 別名(二者唯一)link
2. 復制(二者各一)CTRL+C、CTRL+V,
3. 移動(一有一無)CTRL+X、CTRL+V,
別名對應參考,復制對應資源復制,移動對應資源轉移,
性能的體現,
C++實作
別名語意:reference
復制語意:copy constructor、copy assignment operator,
移動語意:move constructor、move assignment operator,
底層實作都是依賴指標,
設計原則
C++語言設計規則 摘自《C++語言的設計和演化》
"資源使用"上體現的原則:
- 全域平衡
- C++ 的發展必須由實際問題推動
- 對不用的東西不需要付出任何代價(零開銷規則)
簡單的東西依然保持簡單,
總結
C++的兩個坑:1.兼容了C語言的指標 2.拷貝建構式
參考解決了資源使用問題,但無法解決資源管理的問題,
指標是一個潘多拉盒子,要想解決根本問題,只能把盒子拋掉,這個對于C++來說不可能,
但是,C++在如何方便程式員做資源管理這個方向上也做了很多嘗試,
開放問題
Q:“指標思想”是所有編程語言的基石,為什么?
A:
參考資料
https://blog.csdn.net/l477918269/article/details/90233908
https://zhuanlan.zhihu.com/p/374392832
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
https://en.cppreference.com/w/cpp/language/rule_of_three
https://stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves
https://www.zhihu.com/question/363686723/answer/1910830503
https://blog.csdn.net/qq_33113661/article/details/89040579?
https://zhuanlan.zhihu.com/p/265778316
https://stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves
https://www.cnblogs.com/xusd-null/p/3761637.html
https://zhuanlan.zhihu.com/p/265815272
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/412916.html
標籤:其他
