­

C++ 從&到&&

人類發展史,就是不斷挖坑、填坑的過程。
語言發展史也是如此!
任何一門設計合理的語言,給你的限制或提供的什麼特性,都不是沒有代價的。

C的指針

指針:pointer
指針的思想起源於彙編。指針思想是編程思想歷史上的重大飛躍。
每一個編程語言都使用指針。C語言將指針完全暴露給了用戶。潘多拉之盒。

使用指針的必要性:資源管理,即地址管理。

思想層:將地址包了一層。
語法層:T *p; *p;
編譯器:包含一個intptr_t類型成員的結構體。
彙編層:寄存器間接尋址MOV。

image

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引用的彙編視角
image

const引用的彙編視角
image

說明:const引用變量綁定沒有地址的對象時,會生成一個臨時變量/匿名對象來中轉。

全局定義
const int& a = 123; 123的匿名對象在堆上

局部定義
void f{
    const int& a = 456; 456的匿名對象在棧上
}

往下走用*,往上走用&。

C++的第一個坑:兼容了C語言的指針。

C++的構造

3種構造語義:

  1. 構造函數constructor
  2. 拷貝構造copy constructor
  3. 拷貝賦值運算符copy assignment operator

構造函數S()
出廠設置
拷貝構造S(const S &other)
把A的數據複製給B。B(A);
拷貝賦值運算符S& operator=(const S &other)
先把B的資源釋放,再把A的數據複製給B。B=A;

變量按內存分為兩種:

  1. 不包含指針。trivial type。籃球。
  2. 包含指針。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)
在不破壞現有語法規則情況下,你會如何設計?

  1. C++03現有語法不支持移動語義。需要新增移動語義。
  2. 如何標識對象的資源是可以被移動的呢?
  3. 這種機制必須以一種最低開銷的方式實現,並且對所有的類都有效。
  4. 設計在編譯層,與運行層面無關。

C++的設計者們注意到,大多數情況下,右值所包含的對象都是可以安全的被移動的。

左值與右值

左值和右值的概念
CPL語言引入了表達式值的類型value categories這種概念:左值和右值,left or right of assignment。
C語言沿用了類似的分類:左值和其他,locator value and other
C++98 沿用了C語言的分類,但是略微調整。引入了新定義:右值rvalue = non-lvalue。
C++11 新增了xvalue(an 「eXpiring」 value),並調整了之前左值和右值的定義。
image

(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 = false;
    };
    template <typename T> struct is_lvalue_reference<T &> {
        const static bool value = true;
    };
    template <typename T> struct is_rvalue_reference {
        const static bool value = false;
    };
    template <typename T> struct is_rvalue_reference<T &&> {
        const static bool value = true;
    };

    template <typename T> struct is_lvalue {
        const static bool value = is_lvalue_reference<T>::value && (!is_rvalue_reference<T>::value);
    };

    template <typename T> struct is_xvalue {
        const static bool value = (!is_lvalue_reference<T>::value) && is_rvalue_reference<T>::value;
    };

    template <typename T> struct is_prvalue {
        const static bool value = (!is_lvalue_reference<T>::value && !is_rvalue_reference<T>::value);
    };

    template <typename T> struct is_rvalue {
        const static bool value = (is_xvalue<T>::value || is_prvalue<T>::value);
    };

    template <typename T> struct is_glvalue {
        const static bool value = (is_xvalue<T>::value || is_lvalue<T>::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

彙編視角

第一個視角
image

右值引用就是一個指向一個匿名變量的指針。右值引用就是給了外界接觸這個匿名變量的機會。
可見初始化一個右值引用其實是開闢了兩塊空間,一塊是右值引用類型那麼大的匿名變量,一塊是指向這個匿名變量的指針。

第二個視角
image

修改右值引用值的過程也分為兩步,取出指針的值,也就是匿名變量的地址,把右值賦值給地址所指的匿名變量。和修改指針的值一樣的。

Q:如何理解「右值引用延長了右值的生命周期」?
A:右值被放到了一個變量里,已經擁有了內存,當然就避免了被從寄存器里扔出去就消失的命運。這一塊內存的生命周期就和右值引用變量一致了。

最佳實踐

網上找的一個圖:
image

資源使用語義

老師給大寶一個球,大寶拿去玩。老師說:注意些別玩壞了。

球的狀態:可變: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++語言的設計和演化》
“資源使用”上體現的原則:

  1. 全局平衡
  2. C++ 的發展必須由實際問題推動
  3. 對不用的東西不需要付出任何代價(零開銷規則)

簡單的東西依然保持簡單。

總結

C++的兩個坑:1.兼容了C語言的指針 2.拷貝構造函數
引用解決了資源使用問題,但無法解決資源管理的問題。

指針是一個潘多拉盒子,要想解決根本問題,只能把盒子拋掉。這個對於C++來說不可能。
但是,C++在如何方便程序員做資源管理這個方向上也做了很多嘗試。

開放問題

Q:「指針思想」是所有編程語言的基石,為什麼?

參考資料

//blog.csdn.net/l477918269/article/details/90233908
//zhuanlan.zhihu.com/p/374392832
//www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
//en.cppreference.com/w/cpp/language/rule_of_three
//stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves
//www.zhihu.com/question/363686723/answer/1910830503
//blog.csdn.net/qq_33113661/article/details/89040579?
//zhuanlan.zhihu.com/p/265778316
What are the main purposes of using std::forward and which problems it solves?
//www.cnblogs.com/xusd-null/p/3761637.html
//zhuanlan.zhihu.com/p/265815272

Tags: