C++ 從&到&&

天下太平發表於2022-01-17

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

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:“指標思想”是所有程式語言的基石,為什麼?
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

相關文章