C++進階——C++11_右值引用和移動語義_可變參數模板_類的新功能

目錄

1、右值引用和移動語義

1.1 左值和右值

1.2 左值引用和右值引用

1.3 引用延長生命周期

1.4 左值和右值的參數匹配

1.5 右值引用和移動語義的使用場景

1.5.1?左值引用主要使用場景

1.5.2 移動構造和移動賦值

1.5.3 右值引用和移動語義解決傳值返回問題

1.5.4 右值引用和移動語義在傳參中的提效

總結一下:

1.6 類型分類(了解)

1.7 引用折疊

1.7.1 語義折疊的概念

1.7.2 引用折疊的應用

1.8 完美轉發

2、可變參數模板

2.1 基本語法及原理

2.2 包擴展

2.2.1 直接擴展

2.2.2 遞歸擴展

2.2.3?函數調用式包擴展

2.3 emplace系列接口

3、類的新功能

3.1?默認的移動構造和移動賦值

3.2?成員變量聲明時給缺省值

3.3 default和delete

3.4 final和override


1、右值引用和移動語義

C++11之后中新增了的右值引用語法特性,C++11之前學習的引用就叫做左值引用。無論左值引用還是右值引用,都是給對象取別名。?

1.1 左值和右值

左值是一個表示數據的表達式(如變量名解引用的指針等),一般是有持久狀態存儲在內存中可修改,我們可以獲取它的地址左值可以出現在 " = " 的左邊或右邊。定義時const修飾后的左值,不能修改,但是可以取它的地址。

右值也是一個表示數據的表達式(如字面值常量臨時對象等),一般沒有名稱沒有內存地址不可修改右值不能取地址右值只能出現在 " = " 的右邊

左值(lvalue)傳統解釋是 left valueC++11之后,更準確地解釋為 locator value (定位值可以取地址)

右值(rvalue)傳統解釋是 right valueC++11之后,更準確地解釋為 read?value (可讀值不可取地址)

#include<iostream>
using namespace std;
int main()
{// 左值:可以取地址// 以下的 p、b、c、*p、s、s[0] 就是常見的左值int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0] = 'x';cout << &c << endl;cout << (void*)&s[0] << endl; // cout打印char*是打印字符串,轉成void*才能打印地址// 右值:不能取地址// 以下幾個 10、x + y、fmin(x, y)、string("11111") 都是常見的右值double x = 1.1, y = 2.2;10;x + y;fmin(x, y);string("11111");// cout << &10 << endl;// cout << &(x+y) << endl;// cout << &(fmin(x, y)) << endl;// cout << &string("11111") << endl;return 0;
}

1.2 左值引用和右值引用

Type& r1 = x; 就是左值引用,給左值取別名

Type&& rr1 = y; 就是右值引用,給右值取別名

左值引用不能直接引用右值,但是const左值引用可以引用右值

右值引用不能直接引用左值,但是右值引用可以引用move(左值)

template <class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg)
{// forward _Arg as movablereturn static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

move是庫里面的一個函數模板,本質內部是進行強制類型轉換(左值->右值)標記可竊取資源的對象,也可以move(右值),還是右值(增強可讀性),當然還涉及一些引用折疊的知識,這個后面會細講。

注意:變量表達式都是左值屬性,那么,左值引用和右值引用本身是左值可以被修改,如:上面的r1是左值引用,rr1是右值引用,但本身都是左值,可以被修改,那么右值(一般不可修改)就可以通過右值引用進行修改,就可以達到竊取資源的目的

語法層面看,左值引用和右值引用都是取別名不開空間。從匯編底層的角度看下面代碼中r1rr1的匯編層實現,底層都是用指針實現的,所以左值->右值。底層匯編等實現和上層語法表達的意義有時是背離的,所以不要混到一起理解,互相佐證反而會陷入迷途。

#include<iostream>
using namespace std;int main()
{// 左值:可以取地址// 以下的p、b、c、*p、s、s[0]就是常見的左值int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0] = 'x';double x = 1.1, y = 2.2;// 左值引用給左值取別名int& r1 = b;int*& r2 = p;int& r3 = *p;string& r4 = s;char& r5 = s[0];// 右值引用給右值取別名int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);string&& rr4 = string("11111");// 左值引用不能直接引用右值,但是const左值引用可以引用右值const int& rx1 = 10;const double& rx2 = x + y;const double& rx3 = fmin(x, y);const string& rx4 = string("11111");// 右值引用不能直接引用左值,但是右值引用可以引用move(左值)int&& rrx1 = move(b);int*&& rrx2 = move(p);int&& rrx3 = move(*p);string&& rrx4 = move(s);string&& rrx5 = (string&&)s; // 強制類型轉化// b、r1、rr1都是變量表達式,都是左值cout << &b << endl;cout << &r1 << endl;cout << &rr1 << endl;int& r6 = r1;// 這里要注意的是,rr1的屬性是左值,所以不能再被右值引用綁定,除非move一下// int&& rrx6 = rr1;int&& rrx6 = move(rr1);return 0;
}

1.3 引用延長生命周期

注意:引用延長生命周期只能延長當前作用域生命周期

右值引用可用于延長臨時對象的生命周期,右值引用本身是左值,可以被修改

const的左值引用也能延長臨時對象的生命周期,但這些對象無法被修改

#include <iostream>
#include <string>int main()
{std::string s1 = "Test";const std::string& r2 = s1 + s1;       // OK:const左值引用可延長臨時對象生命周期// r2 += "Test";                       // 錯誤:不能通過const引用修改std::string&& r3 = s1 + s1;            // OK:右值引用延長臨時對象生存期r3 += "Test";                          // OK:可通過非const右值引用修改std::cout << r3 << '\n';return 0;
}

1.4 左值和右值的參數匹配

C++98中,我們實現一個const左值引用作為形參的函數,那么實參傳遞左值和右值都可以匹配

C++11以后,分別重載左值引用const左值引用右值引用作為形參的 f 函數,會調用最匹配的

#include <iostream>
using namespace std;void f(int& x) {cout << "左值引用重載 f(" << x << ")\n";
}void f(const int& x) {cout << "const左值引用重載 f(" << x << ")\n";
}void f(int&& x) {cout << "右值引用重載 f(" << x << ")\n";
}int main() {int i = 1;const int ci = 2;f(i);             // 調用 f(int&)f(ci);            // 調用 f(const int&)f(3);             // 調用 f(int&&),若無此重載則調用 f(const int&)f(std::move(i));  // 調用 f(int&&)// 右值引用變量在表達式中是左值int&& x = 1;f(x);             // 調用 f(int&)f(std::move(x));  // 調用 f(int&&)return 0;
}

1.5 右值引用和移動語義的使用場景

1.5.1?左值引用主要使用場景

左值引用主要使用場景是在函數中左值引用傳參左值引用傳返回值減少拷貝,同時還可以修改實參和修改返回對象的價值。

但是有些場景不能使用傳左值引用返回,如 addStrings 和 generate 函數,C++98 中的解決方案只能是被迫使用輸出型參數解決。那么 C++11 以后這里可以使用右值引用做返回值解決嗎?顯然是不能的,因為這里的本質是返回對象是一個局部對象函數結束這個對象就析構銷毀了,右值引用返回,只能延長對象在當前函數棧幀的生命周期,但函數棧幀已經銷毀了,對象會析構,無力回天了。

class Solution {
public:// 傳值返回需要拷貝string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1;int end2 = num2.size() - 1;// 進位int next = 0;while (end1 >= 0 || end2 >= 0) {int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1) {str += '1';}reverse(str.begin(), str.end());return str;}// 這里的傳值返回拷貝代價就太大了vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i){vv[i].resize(i + 1, 1);}for (int i = 2; i < numRows; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}return vv;}
};
1.5.2 移動構造和移動賦值

移動構造是一種構造函數類似拷貝構造函數

移動構造函數要求第一個參數該類類型對象右值引用,后面只能加缺省參數。

移動賦值是一個賦值運算符重載類似拷貝賦值函數

移動賦值函數要求第一個參數該類類型對象右值引用

對于像 string/vector 這樣的深拷貝的類或者包含深拷貝的成員變量的類移動構造和移動賦值才有意義,因為移動構造移動賦值第一個參數都是右值引用的類型,他的本質是要“竊取”引用的右值對象的資源(右值對象一般是臨時對象(返回的局部對象也認為是臨時對象),直接swap臨時對象的資源,不走深拷貝),從提高效率

注意:

1. move(左值)是讓左值->右值本身沒有竊取資源,是移動構造和移動賦值竊取(swap)資源

2. 對于內置類型,只需賦值就行,就算是移動,竊取的本質也是賦值,所以不需要移動語義

3. 個人疑惑,移動構造為什么不叫移動拷貝構造,不是用右值對象構造的嗎?因為拷貝有不改變原對象的意思,避免混淆。

下面的 Lzc::string 樣例實現了移動構造和移動賦值,我們需要結合場景理解。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <string.h>
#include <algorithm>
using namespace std;namespace Lzc
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin() { return _str; }iterator end() { return _str + _size; }const_iterator begin() const { return _str; }const_iterator end() const { return _str + _size; }string(const char* str = ""): _size(strlen(str)), _capacity(_size){cout << "string(char* str) 構造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string(const string& s){cout << "string(const string& s) -- 拷貝構造" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}// 移動構造string(string&& s){cout << "string(string&& s) -- 移動構造" << endl;swap(s);}string& operator=(const string& s){cout << "string& operator=(const string& s) -- 拷貝賦值" << endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}}return *this;}// 移動賦值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移動賦值" << endl;swap(s);return *this;}~string(){cout << "~string() -- 析構" << endl;delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];if (_str){strcpy(tmp, _str);delete[] _str;}_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const { return _str; }size_t size() const { return _size; }private:char* _str = new char('\0');size_t _size = 0;size_t _capacity = 0;};
}int main()
{// 構造Lzc::string s1("xxxxx");// 拷貝構造Lzc::string s2 = s1;// 構造+移動構造,優化后直接構造Lzc::string s3 = Lzc::string("yyyyy");// 移動構造Lzc::string s4 = move(s1);cout << "******************************" << endl;return 0;
}
1.5.3 右值引用和移動語義解決傳值返回問題
namespace Lzc {string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0;while (end1 >= 0 || end2 >= 0) {int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());cout << "******************************" << endl;return str;}
}int main() {Lzc::string ret;ret = Lzc::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}
g++ -std=c++11 test.cpp -fno-elide-constructors -o test && ./test

使用C++11標準,去掉編譯器優化,編譯為test,并執行。

1.5.4 右值引用和移動語義在傳參中的提效

查看STL文檔我們發現,C++11以后容器的push系列insert系列的接口都增加了右值引用版本。

實參是一個左值時,左值引用,容器內部繼續調用拷貝構造進行拷貝,將拷貝的對象放到容器空間中。

實參是一個右值時,右值引用,容器內部則調用移動構造(由于右值引用本身是左值,會走拷貝構造,需move,轉成右值,走移動構造),將竊取臨時對象的資源的對象放到容器空間中。

	template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()):_data(data) // 拷貝構造,_next(nullptr),_prev(nullptr){}list_node(T&& data):_data(move(data))// 移動構造, _next(nullptr), _prev(nullptr){}};

其實這里還有一個emplace系列的接口,但是這個涉及可變參數模板,我們需要把可變參數模板講解以后再講解emplace系列的接口。

總結一下:

編譯器優化不是C++的標準取決于編譯器

右值引用+移動語義編譯器優化差別不大有時略勝一籌只需支持C++11的右值引用和移動語義不依賴編譯器

但是右值引用+移動語義 與 編譯器優化 = 完美

多嘴一句:因為之前的C++委員會有點"擺爛",沒有出右值引用和移動語義,所以編譯器自己優化,所以現在理解有點難受。

1.6 類型分類(了解)

中文:值類別 - cppreference.com

英文:Value categories - cppreference.com

一般看,左值,右值。

  • 左值(lvalue)?是指具有持久性有名字的表達式可以取地址,通常代表一個對象的內存位置。

  • 純右值(prvalue)?是臨時對象,通常是計算過程中產生的中間結果,沒有名字不能取地址

  • 將亡值(xvalue)?是即將被move的對象,通常是“可以被竊取資源”的右值。

?

1.7 引用折疊

1.7.1 語義折疊的概念

1. C++不能直接定義引用的引用,如 int& && r = i; 這樣寫會直接報錯

2. 模板typedef中的類型操作可以構成引用的引用。 這時C++11給出了一個引用折疊的規則:右值引用右值引用折疊成右值引用所有其他組合均折疊成左值引用。?

#include <iostream>// 由于引用折疊限定,f1 實例化以后總是一個左值引用
template<class T>
void f1(T& x) {}// 由于引用折疊限定,f2 實例化后可以是左值引用,也可以是右值引用
// 也稱: 萬能引用
template<class T>
void f2(T&& x) {}int main() {typedef int& lref;typedef int&& rref;int n = 0;// 引用折疊示例lref& r1 = n;  // r1 的類型是 int&lref&& r2 = n; // r2 的類型是 int&rref& r3 = n;  // r3 的類型是 int&rref&& r4 = 1; // r4 的類型是 int&&// f1 函數模板實例化與調用情況// 沒有折疊 -> 實例化為 void f1(int& x)f1<int>(n);// f1<int>(0);  // 報錯,不能將右值綁定到左值引用// 折疊 -> 實例化為 void f1(int& x)f1<int&>(n);// f1<int&>(0); // 報錯,不能將右值綁定到左值引用// 折疊 -> 實例化為 void f1(int& x)f1<int&&>(n);// f1<int&&>(0); // 報錯,不能將右值綁定到左值引用// 折疊 -> 實例化為 void f1(const int& x)f1<const int&>(n);f1<const int&>(0);// 折疊 -> 實例化為 void f1(const int& x)f1<const int&&>(n);f1<const int&&>(0);// f2 函數模板實例化與調用情況// 沒有折疊 -> 實例化為 void f2(int&& x)// f2<int>(n);   // 報錯,不能將左值綁定到右值引用f2<int>(0);// 折疊 -> 實例化為 void f2(int& x)f2<int&>(n);// f2<int&>(0);  // 報錯,不能將右值綁定到左值引用// 折疊 -> 實例化為 void f2(int&& x)// f2<int&&>(n); // 報錯,不能將左值綁定到右值引用f2<int&&>(0);return 0;
}

個人疑惑:

const int&& x,x是右值引用,右值一般不可修改,因為x本身是左值,所以可以修改,如果還加const,那就是右值不可修改,這用說嗎?而且只可以接受右值。

我const int& x,也是不可修改,還可以接受左值和右值。

但是一般使用函數模板,不顯示實例化,

那么萬能引用函數模板,的推導過程是:

傳右值T就是右值的類型T&&就是右值引用

傳左值T就是左值類型左值引用T&&就是左值引用。?

#include <iostream>
#include <utility> // movetemplate<class T>
void Function(T&& t) {int a = 0;T x = a;// x++;std::cout << &a << std::endl;std::cout << &x << std::endl << std::endl;
}int main() {// 10 是右值,推導出 T 為 int,模板實例化為 void Function(int&& t)Function(10);int a;// a 是左值,推導出 T 為 int&,引用折疊,模板實例化為 void Function(int& t)Function(a);// std::move(a) 是右值,推導出 T 為 int,模板實例化為 void Function(int&& t)Function(std::move(a));const int b = 8;// b 是 const 左值,推導出 T 為 const int&,引用折疊,模板實例化為 void Function(const int& t)// Function 內部會編譯報錯,因為 x 不能 ++Function(b);// std::move(b) 是 const 右值,推導出 T 為 const int,模板實例化為 void Function(const int&& t)// 所以 Function 內部會編譯報錯,x 不能 ++Function(std::move(b));return 0;
}
1.7.2 引用折疊的應用

左值引用和右值引用的函數,只有參數部分不同,函數體基本相同,高度相似->模板

例:

template<class T> list{ };中的push_back,此時T&&不是萬能引用,因為list模板實例化了,T就已經確定了。要再加一層模板,才能構成萬能引用。

            void push_back(const T& x){insert(end(), x);}void push_back(T&& x){insert(end(), move(x)); // x本身是左值}

那么list模板里面再來個函數模板,這個時候萬能引用就體現出價值了,

傳左值就實例化左值引用版本傳右值就實例化右值引用版本

但是有一個問題不能直接move(x),因為可能是左值引用版本,若move(x),原對象的資源可能被竊取,改變了原對象。

  • 左值 → 會調用拷貝語義(保留原對象)。

  • 右值 → 會調用移動語義(允許“竊取”資源)。

		// 萬能引用template<class X>void push_back(X&& x){insert(end(), x);}

這個時候就需要完美轉發了,右值引用(本身是左值)返回右值引用,左值引用返回左值引用,然后再傳。?

其實,這個邏輯沒錯,但是例子有點小瑕疵,如果是list<pair<int,int>>,沒有萬能引用,一開始就確定了T是pair<int,int>,可以push_back({1,2}),走類型轉換,但是如果寫成萬能引用,就不能push_back({1,2}),因為在傳參時,確定類型,不知道{1,2}是什么類型。

1.8 完美轉發

// 左值版本(T 是具體類型,如 int&)
template<typename T>
constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept {return static_cast<T&&>(t);
}// 右值版本(T 是具體類型,如 int&&)
template<typename T>
constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept {static_assert(!std::is_lvalue_reference<T>::value, "Cannot forward rvalue as lvalue");return static_cast<T&&>(t);
}

std::remove_reference_t<T>&?和?typename std::remove_reference<T>::type&?在功能上是完全等價的?

形式說明引入標準
typename std::remove_reference<T>::type&傳統的 traits 用法,需要?typename?關鍵字C++98/03
std::remove_reference_t<T>&C++14 引入的簡化寫法,_t?后綴表示直接取類型C++14

remove_reference_t<T>:T變成非引用類型(去掉了傳過來的T中的&)。

static_cast<T&&>(x):T&&會引用折疊,x強制轉成T&&類型

當?T?是非引用類型時(即原始參數是右值),選擇過程如下:

情況1:傳入?右值(如?std::forward<int>(10)

  1. 模板參數?T?被推導為?int(非引用類型)

  2. 匹配過程:

    • 左值版本參數:remove_reference_t<int>&?→?int&
      ? 無法綁定到右值?10

    • 右值版本參數:remove_reference_t<int>&&?→?int&&
      ? 精確匹配右值

  3. 選擇右值版本

? static_cast<T&&>?→ 生成右值引用類型表達式

? ? ?編譯器魔法這個表達式會被特殊標記為"可以匹配右值引用參數"。

情況2:傳入?左值(如?int x; std::forward<int&>(x)

  1. 模板參數?T?被推導為?int&(左值引用)

  2. 匹配過程:

    • 左值版本參數:remove_reference_t<int&>&?→?int&
      ? 精確匹配左值

    • 右值版本參數:remove_reference_t<int&>&&?→?int&&
      ? 無法綁定左值

  3. 選擇左值版本

static_cast<T&&>?→ 生成左值引用類型表達式

所以:

		// 萬能引用template<class X>void push_back(X&& x){insert(end(), forward<T>(x));}

注意:萬能引用進行傳參時,通常需要完美轉發

    2、可變參數模板

    2.1 基本語法及原理

    C++11 支持可變參數模板,也就是說支持可變數量參數的函數模板和類模板,可變數目的參數被稱為參數包,存在兩種參數包:模板參數包,表示零或多個模板參數函數參數包:表示零或多個函數參數

    template <class ...Args> void Func (Args... args) {}
    template <class ...Args> void Func (Args&... args) {}
    template <class ...Args> void Func (Args&&... args) {}

    我們用省略號表示一個模板參數或函數參數的一個

    模板參數列表中,class... typename... 指出接下來的參數表示零或多個類型

    函數參數列表中,類型名... 指出接下來的參數表示零或多個參數

    函數參數包可以用左值引用或右值引用表示,跟前面普通模板一樣,每個參數實例化時遵循引用折疊規則。

    可變參數模板的原理跟普通模板類似,本質還是去實例化對應類型和個數的多個函數。

    這里我們可以使用sizeof...運算符去計算參數包中參數的個數

    template <class ...Args>
    void Print(Args&&... args) {cout << sizeof...(args) << endl;
    }int main() {double x = 2.2;Print();                        // 包里有0個參數Print(1);                       // 包里有1個參數Print(1, string("xxxxx"));      // 包里有2個參數Print(1.1, string("xxxxx"), x); // 包里有3個參數return 0;
    }// 原理1:編譯本質這里會結合引用折疊規則實例化出以下四個函數
    void Print();
    void Print(int&& arg1);
    void Print(int&& arg1, string&& arg2);
    void Print(double&& arg1, string&& arg2, double& arg3);// 原理2:更本質去看沒有可變參數模板,我們實現出這樣的多個函數模板才能支持
    // 這里的功能,有了可變參數模板,我們進一步被解放,他是類型泛化基礎
    // 上疊加數量變化,讓我們泛型編程更靈活。
    template <class T1>
    void Print(T1&& arg1);template <class T1, class T2>
    void Print(T1&& arg1, T2&& arg2);template <class T1, class T2, class T3>
    void Print(T1&& arg1, T2&& arg2, T3&& arg3);
    // ...

    2.2 包擴展

    對于一個參數包,我們除了能計算它的參數個數,我們能做的唯一的事情就是擴展它

    // 可變模板參數
    // 參數類型可變
    // 參數個數可變// 打印參數包內容// template <class... Args>
    // void Print(Args... args)
    // {
    //     可變參數模板編譯時解析
    //             
    //     下面是運行獲取和解析,所以不支持這樣用
    //     cout << sizeof...(args) << endl;
    //     for (size_t i = 0; i < sizeof...(args); i++)
    //     {
    //         cout << args[i] << " ";  // 不支持這樣用
    //     }
    //     cout << endl;
    // }
    2.2.1 直接擴展
    template<class... Args>
    void Print(Args... args) {// 完全展開:生成與args數量相同的參數SomeFunc(args...); 
    }Print(1, "abc", 2.0); 
    // 展開為:SomeFunc(1, "abc", 2.0);
    2.2.2 遞歸擴展

    模板是寫給編譯器的。

    #include <iostream>
    #include <string>using namespace std;void ShowList() {// 編譯器時遞歸的終止條件,參數包是0個時,直接匹配這個函數cout << endl;
    }// 傳過來的args,是N個參數的參數包
    // 調用ShowList,參數包的第一個傳給x,剩下N-1傳給第二個參數包
    template <class T, class... Args>
    void ShowList(T x, Args... args) {cout << x << " ";ShowList(args...); // 觸發擴展操作
    }// 編譯時遞歸推導解析參數
    template <class... Args>
    void Print(Args... args) {ShowList(args...); // 觸發擴展操作
    }int main() {Print(1, string("xxxxx"), 2.2);return 0;
    }// Print(1, string("xxxxx"), 2.2);調用時
    // 本質編譯器將可變參數模板通過模式的包擴展,編譯器推導的以下三個重載函數函數
    // void ShowList(double x)
    // {
    //     cout << x << " ";
    //     ShowList();
    // }
    //
    // void ShowList(string x, double z)
    // {
    //     cout << x << " ";
    //     ShowList(z);
    // }
    //
    // void ShowList(int x, string y, double z)
    // {
    //     cout << x << " ";
    //     ShowList(y, z);
    // }
    //
    // void Print(int x, string y, double z)
    // {
    //     ShowList(x, y, z);
    // }
    2.2.3?函數調用式包擴展

    和直接擴展相比,可以對每個參數預處理

    template <class T>
    const T& GetArg(const T& x) {cout << x << " ";return x;
    }template <class ...Args>
    void Arguments(Args... args) {}template <class ...Args>
    void Print(Args... args) {// 注意GetArg必須返回接收到的對象,這樣才能組成參數包給ArgumentsArguments(GetArg(args)...);
    }// void Print(int x, string y, double z)
    // {
    // }
    // 本質可以理解為編譯器編譯時,包的擴展模式
    // 將上面的函數模板擴展實例化為下面的函數
    // 是不是很抽象,C++11以后,只能說委員會的大佬設計語法思維跳躍得太厲害
    // Arguments(GetArg(x), GetArg(y), GetArg(z));int main() {Print(1, string("xxxxx"), 2.2);return 0;
    }

    2.3 emplace系列接口

    template <class... Args> void emplace_back (Args&&... args);
    template <class... Args> iterator emplace (const_iterator position
    , Args&&... args);

    C++11 以后 STL 容器新增了 emplace 系列的接口,emplace 系列的接口均為可變參數模板,功能上兼容 pushinsert 系列。假設容器為 container<T>,emplace 還支持直接插入構造 T 對象的參數,可以直接在容器空間上構造 T 對象,高效一些。

    下面我們模擬實現了 list emplace emplace_back 接口,這里把參數包不斷往下傳遞,最終在節點的構造中直接去匹配容器存儲的數據類型 T 構造可以直接在容器空間上構造 T 對象

    傳遞參數包過程中,如果是Args&&... args的萬能引用參數包,要用完美轉發參數包,方式如下
    std::forward<Args>(args)...std::forward?分別應用到?args?中的每一個參數上。否則編譯時包擴展后右值引用變量表達式就變成了左值。?

    emplace直接在容器內構造對象,避免臨時對象的創建和拷貝/移動,因此在多數情況下比push/insert高效

    如:下面的push_back和insert沒有實現萬能引用,對于list<pair<int,int>>,push_back({1,2})可以類型轉換,萬能引用就不能類型轉換(不知道{1,2}是什么類型),更靈活。

    #pragma once
    #include<assert.h>namespace Lzc
    {template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node() = default;template <class... Args>list_node(Args&&... args): _next(nullptr), _prev(nullptr), _data(std::forward<Args>(args)...){}};template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};template<class T>class list{typedef list_node<T> Node;public:typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}void push_back(const T& x){insert(end(), x);}void push_back(T&& x){insert(end(), forward<T>(x));}// 萬能引用/*template<class X>void push_back(X&& x){insert(end(), forward<X>(x));}*/template <class... Args>void emplace_back(Args&&... args){insert(end(), std::forward<Args>(args)...);}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);// prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}iterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(forward<T>(x));// prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}// 萬能引用//template<class X>//iterator insert(iterator pos, X&& x)//{//	Node* cur = pos._node;//	Node* prev = cur->_prev;//	Node* newnode = new Node(forward<X>(x));//	// prev newnode cur//	newnode->_next = cur;//	cur->_prev = newnode;//	newnode->_prev = prev;//	prev->_next = newnode;//	++_size;//	return newnode;//}template <class... Args>iterator insert(iterator pos, Args&&... args){Node* cur = pos._node;Node* newnode = new Node(std::forward<Args>(args)...);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}size_t size() const{return _size;}bool empty() const{return _size == 0;}private:Node* _head;size_t _size;};
    }

    3、類的新功能

    3.1?默認的移動構造和移動賦值

    原來 C++ 類中,有 6 個默認成員函數:構造函數 / 析構函數 / 拷貝構造函數 / 拷貝賦值重載 / 取地址重載 /const 取地址重載,最重要的是前 4 個,后兩個用處不大,默認成員函數就是我們不寫編譯器會生成一個默認的。C++11 新增兩個默認成員函數移動構造函數移動賦值運算符重載

    如果你自己沒有實現移動構造函數,沒有實現析構函數拷貝構造拷貝賦值重載中的任意一
    (因為這幾個是綁定到一起的,都不寫,說明默認生成的就夠用了)。那么編譯器會自動生成一個默認移動構造。默認生成的移動構造函數,對于內置類型成員會執
    行逐成員按字節拷貝(淺拷貝),自定義類型成員,如果實現了移動構造調用移動構造沒有實現調用拷貝構造

    如果你自己沒有實現移動賦值重載函數,且沒有實現析構函數、拷貝構造、拷貝賦值重載中的任意
    一個,那么編譯器會自動生成一個默認移動賦值。默認生成的移動賦值函數,對于內置類型成員會
    執行逐成員按字節拷貝(淺拷貝),自定義類型成員,則需要看這個成員是否實現移動賦值,如果實現了就調用移動賦值,沒有實現就調用拷貝賦值。(默認移動賦值移動構造完全類似)

    如果你自己實現移動構造或者移動賦值編譯器不會自動提供拷貝構造和拷貝賦值

    3.2?成員變量聲明時給缺省值

    C++初階——類和對象(下)-CSDN博客

    3.3 default和delete

    C++11 可以讓你更好地控制要使用的默認函數。假設你要使用某個默認的函數,但是因為一些原因這個函數沒有默認生成,可以使用?default 關鍵字顯式指定生成,比如:我們提供了拷貝構造,就不會生成移動構造了,那么我們可以使用 default 關鍵字顯式指定移動構造生成

    C++11如果想要限制某些默認函數的生成,只需在該函數聲明加上 = delete ,該語法指示編譯器不生成對應函數的默認版本,稱 = delete 修飾的函數刪除函數

    class Person {
    public:Person(const char* name = "", int age = 0): _name(name),_age(age) {}Person(const Person& p): _name(p._name),_age(p._age) {}Person(Person&& p) = default;// Person(const Person& p) = delete;private:bit::string _name;int _age;
    };int main() {Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
    }

    3.4 final和override

    C++進階——多態-CSDN博客

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/diannao/77487.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/diannao/77487.shtml
    英文地址,請注明出處:http://en.pswp.cn/diannao/77487.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    HTTP協議原理深度解析:從基礎到實踐

    引言 在互聯網技術體系中,HTTP(HyperText Transfer Protocol)協議如同數字世界的"通用語言",支撐著全球超50億網民的日常網絡交互。作為爬蟲開發、Web應用構建的核心技術基礎,理解HTTP原理是每個開發者必須掌握的技能。本文將從協議本質、技術演進、安全機制三…

    Web品質 - 重要的HTML元素

    Web品質 - 重要的HTML元素 在構建一個優秀的Web頁面時,HTML元素的選擇和運用至關重要。這些元素不僅影響頁面的結構,還直接關系到頁面的可用性、可訪問性和SEO表現。本文將深入探討一些關鍵的HTML元素,并解釋它們在提升Web品質方面的重要性。 1. <html> 根元素 HTM…

    【AI提示詞】競品分析專家

    提示說明 對產品進行競品分析&#xff0c;明確產品定位和優化營銷策略。 提示詞 # 角色:競品分析專家## 背景: 需要對旗下產品A進行競品分析,明確產品定位和優化營銷策略。## 描述: - 作者:張三 - 版本:1.0 - 語言:中文## 注意事項: 保持客觀公正態度,用數據說話,給出具體的…

    4-6記錄(B樹)

    找左邊右下或者右邊左下 轉化成了前驅后繼的刪除 又分好幾種情況&#xff1a; 1. 只剩25&#xff0c;小于2&#xff0c;所以把父親拉到25旁邊&#xff0c;兄弟的70頂替父親 對于25&#xff0c;25的后繼就是70&#xff0c;25后繼的后繼是71&#xff08;中序遍歷) 2. 借左子樹…

    什么是RACI矩陣,應用在什么場景?

    一、什么是RACI RACI矩陣是一種用于明確項目或任務中角色與責任的管理工具&#xff0c;通過定義不同人員在任務中的參與程度來避免職責不清的問題。以下是其核心要點&#xff1a; ?RACI的含義? ● ?R&#xff08;Responsible&#xff09;執行者?&#xff1a;直接完成任務…

    深入理解全排列算法:DFS與回溯的完美結合

    全排列問題是算法中的經典問題&#xff0c;其目標是將一組數字的所有可能排列組合列舉出來。本文將詳細解析如何通過深度優先搜索&#xff08;DFS&#xff09;和回溯法高效生成全排列&#xff0c;并通過模擬遞歸過程幫助讀者徹底掌握其核心思想。 問題描述 給定一個正整數 n&a…

    在 Dev-C++中編譯運行GUI 程序介紹(二)示例:祝福程序

    在 Dev-C中編譯運行GUI 程序介紹&#xff08;二&#xff09;示例&#xff1a;祝福程序 前期見&#xff1a; 在 Dev-C中編譯運行GUI 程序介紹&#xff08;一&#xff09;基礎 https://blog.csdn.net/cnds123/article/details/147019078 示例1、祝福程序 本文中的這個祝福程序是…

    Stable Diffusion 四重調參優化——項目學習記錄

    學習記錄還原&#xff1a;在本次實驗中&#xff0c;我基于 Stable Diffusion v1.5模型&#xff0c;通過一系列優化方法提升生成圖像的質量&#xff0c;最終實現了圖像質量的顯著提升。實驗從基礎的 Img2Img 技術入手&#xff0c;逐步推進到參數微調、DreamShaper 模型和 Contro…

    Solidity智能合約漏洞類型與解題思路指南

    一、常見漏洞類型與通俗解釋 1. 重入攻擊(Reentrancy) ?? 通俗解釋:就像你去銀行取錢,柜臺人員先給你錢,然后再記賬。你拿到錢后立即又要求取錢,由于賬還沒記,柜臺又給你一次錢,這樣循環下去你就能拿走銀行所有的錢。 漏洞原理:合約在更新狀態前調用外部合約,允許…

    Docker部署.NetCore8項目

    在VS.net新建.netCore8項目&#xff0c;生成項目的發布文件&#xff0c;之后添加Dockerfile&#xff0c;內容如下&#xff1a; FROM mcr.microsoft.com/dotnet/aspnet:8.0 # 設置工作目錄 WORKDIR /app # 掛載臨時卷&#xff08;類似于 VOLUME /tmp&#xff09; VOLUME /tmp …

    【C++】右值引用、移動語義與完美轉發

    左值、右值是C常見的概念&#xff0c;那么什么是右值引用&#xff0c;移動語義&#xff0c;完美轉發呢&#xff1f;本UP帶大家了解一下C校招常問的C11新特性。 左值與右值 左值&#xff1a;明確存儲未知、可以取地址的表達式 右值&#xff1a;臨時的、即將被銷毀的&#xff…

    艾爾登法環地圖不能使用鼠標移動或點擊傳送點原因和設置方法

    今天玩艾爾登法環突發發現地圖不能用鼠標點擊傳送點了。 找了半天發現設置地圖選單的游標移動方式只有鍵盤了&#xff0c;改成鍵盤與鼠標就好啦。

    【算法】——一鍵解決動態規劃

    前言 動態規劃是一種高效解決??重疊子問題??和??最優子結構??問題的算法思想。它通過??分治記憶化??&#xff0c;將復雜問題分解為子問題&#xff0c;并存儲中間結果&#xff0c;避免重復計算&#xff0c;從而大幅提升效率。 ??為什么重要&#xff1f;? ??優化…

    uniApp開發微信小程序-連接藍牙連接打印機上岸!

    歷經波折三次成功上岸&#xff01; 三次經歷簡單絮叨一下&#xff1a;使用uniAppvue開發的微信小程序&#xff0c;使用藍牙連接打印機&#xff0c;藍牙所有的接口都是插件中封裝的&#xff0c;用的插件市場中的這個&#xff1a; dothan-lpapi-ble &#xff1b;所以&#xff0c…

    軟件系統安全設計方案,信息化安全建設方案(Word原件)

    1.1 總體設計 1.1.1 設計原則 1.2 物理層安全 1.2.1 機房建設安全 1.2.2 電氣安全特性 1.2.3 設備安全 1.2.4 介質安全措施 1.3 網絡層安全 1.3.1 網絡結構安全 1.3.2 劃分子網絡 1.3.3 異常流量管理 1.3.4 網絡安全審計 1.3.5 網絡訪問控制 1.3.6 完…

    wsl2+ubuntu22.04安裝blenderproc教程

    本章教程,介紹如何在windows操作系統上通過wsl2+Ubuntu22.04上安裝blenderproc。 一、pipi安裝方式 推薦使用minconda3安裝Python環境。 pip install Blenderproc二、源碼安裝 1、下載源碼 git clone https://github.com/DLR-RM/BlenderProc2、安裝依賴 cd BlenderProc &am…

    Blender 轉 STL 文件全攻略:從基礎到進階

    在 3D 建模與打印領域&#xff0c;Blender 憑借其強大的功能和開源特性&#xff0c;深受創作者喜愛。而 STL 文件格式&#xff0c;作為 3D 打印行業的通用標準&#xff0c;能被絕大多數 3D 打印軟件和設備所識別。因此&#xff0c;將 Blender 模型轉換為 STL 文件&#xff0c;是…

    Ansys Electronics 變壓器 ACT

    你好&#xff0c; 在本博客中&#xff0c;我將討論如何使用 Ansys 電子變壓器 ACT 自動快速地設計電力電子電感器或變壓器。我將逐步介紹設計和創建電力電子變壓器示例的步驟&#xff0c;該變壓器為同心組件&#xff0c;雙繞組&#xff0c;采用正弦電壓激勵&#xff0c;并應用…

    nacos配置達夢數據庫驅動源代碼步驟

    1.在父工程pom.xml添加依賴&#xff1a; <dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.1.193</version> </dependency> 2.在nacos-config模塊pom.xml添加依賴&#xff1…

    4.9-4.10學習總結 Stream流練習+方法引用+異常

    Stream流練習&#xff1a; 1.打印數組內的偶數。 import java.util.*; import java.util.function.BiConsumer; public class test {public static void main(String[] args) {ArrayList<Integer> listnew ArrayList<>();Collections.addAll(list,1,2,3,4,5,6,7,…