国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式

這篇具有很好參考價(jià)值的文章主要介紹了【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

1. 右值引用和移動(dòng)語義

1.1 左值引用和右值引用

在C++11之前,我們只有引用的概念,沒有接觸到所謂的左值引用或者是右值引用這種概念,從C++11開始,增加了右值引用的概念,那么現(xiàn)在我們將對(duì)引用進(jìn)行一個(gè)概念上的區(qū)分。在此之前我們所說的引用都是左值引用,對(duì)于左值引用相關(guān)的內(nèi)容,可以去看一看博主之前寫的文章C++引用。

不管是左值引用還是右值引用,本質(zhì)上都是給對(duì)象取別名

那么,怎么區(qū)別左值引用和右值引用呢?

左值是一個(gè)表示數(shù)據(jù)的表達(dá)式(一個(gè)變量名或者是解引用的指針),我們可以獲取到他的地址+可以對(duì)它進(jìn)行賦值,左值可以在等號(hào)的左邊,右值不能在等號(hào)的左邊,const修飾的變量不能被賦值,但是能夠取地址。

右值也是一個(gè)表達(dá)式,如:字面量,表達(dá)式的返回值,函數(shù)的返回值,右值能夠出現(xiàn)在賦值符號(hào)的右邊,不能出現(xiàn)在賦值符號(hào)的左邊,右值不能取地址。

左值引用就是對(duì)左值進(jìn)行取別名操作,右值引用就是對(duì)右值取別名

void Test1()
{
    //左值
    int a = 1;
    double x = 1.1, y = 2.2;
    int* pb = new int(10);
    const int c = 2;
    //左值引用
    int& ra = a;
    int*& rpb = pb;
    const int& rc = c;
    int& pvalue = *pb;
    //右值
    10;
    x + y;
    min(x, y);
    //右值引用
    int&& rr1 = 10;
    int&& rr2 = x + y;
    int&& rr3 = min(x, y);
}

一個(gè)有趣的現(xiàn)象:

我們知道,右值是不能被賦值的,但是看下面這段代碼

void Test2()
{
    int x = 1, y = 2;
    int&& rr1 = x + y;
    cout << "x + y:" << x + y << endl;
    cout << "rr1:" << rr1 << endl;
    rr1 = 10;
    cout << "rr1" << rr1 << endl;
}

右值x+y在被右值引用之后就可以被賦值了,即變成了左值

這是因?yàn)?strong>在給右值取別名之后,會(huì)被存儲(chǔ)在一個(gè)特定的位置,然后就能取到該位置的地址,因此也就能更改次地址存放的值。如果不想讓它能被能改就可以使用const修飾右值引用。當(dāng)然實(shí)際應(yīng)用中不會(huì)使用到這個(gè)特性,所以這個(gè)特性也就不重要

1.2 左值引用和右值引用的比較

左值引用的總結(jié):

  • 左值引用只能引用左值,不能引用右值
  • const左值引用既能引用左值,也能引用右值(這個(gè)跟我們之前所說的臨時(shí)變量具有常性可以對(duì)照,那個(gè)臨時(shí)變量就是右值)
void Test3()
{
    //左值引用只能引用左值,不能引用右值
    int a = 10;
    int& ra1 = a;
    //int& ra2 = 10;//右值引用不能引用左值,因此這行代碼報(bào)錯(cuò)
  
    //const左值引用既能引用左值,也能引用右值
    const int& ra3 = 10;//引用右值
    const int& ra4 = a;//引用左值
}

右值引用的總結(jié):

  • 右值引用只能引用右值,不能引用左值
  • move函數(shù)可以將左值變?yōu)橛抑?,因此右值引用可以引用move之后的左值
void Test4()
{
    //右值引用只能引用右值,不能引用左值
    int&& r1 = 10;
    int a = 10;
    //int&& r2 = a;//右值引用引用左值報(bào)錯(cuò)
    //Xcode報(bào)錯(cuò)內(nèi)容:Rvalue reference to type 'int' cannot bind to lvalue of type 'int'(無法將左值綁定到右值引用)
  
    //右值引用能引用move之后的左值
    int&& r3 = std::move(a);
}

1.3右值引用的使用場(chǎng)景和意義

既然在C++11之前已經(jīng)有了左值引用,為什么還要加上右值引用這種概念呢?不是“畫蛇添足”嗎?

首先我們來總結(jié)一下左值引用的好處

  • 做函數(shù)參數(shù):能夠減少拷貝,提高效率,可以作為輸出型參數(shù)
  • 做返回值:能夠減少拷貝,提高效率

雖然左值引用有以上優(yōu)點(diǎn),但是,如果遇到下面的狀況:

//狀況一
template<class T>
T func(const T& val)
{
    T ret;
    //...
    return ret;
}
//狀況二
void Test5()
{
    zht::string str;
    str = zht::to_string(-1234);
}

此時(shí)ret和str出了作用域之后就會(huì)銷毀,如果T是類似string的對(duì)象,就需要進(jìn)行深拷貝,所以就會(huì)造成效率降低。

這里我們采用自己實(shí)現(xiàn)的string,能夠更清晰的看到調(diào)用情況:在這個(gè)string中增加了一些輸出信息

namespace  zht
{
class string
{
public:
    typedef char* iterator;
    iterator begin()
    {
        return _str;
    }
    iterator end()
    {
        return _str + _size;
    }
    string(const char* str = "")
        :_size(strlen(str))
        , _capacity(_size)
    {
        cout << "string(const char* str = "") -- 構(gòu)造函數(shù)" << endl;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }
    // s1.swap(s2)
    void swap(string& s)
    {
        std::swap(_str, s._str);
        std::swap(_size, s._size);
        std::swap(_capacity, s._capacity);
    }
    // 拷貝構(gòu)造
    string(const string& s)
        :_str(nullptr)
    {
        cout << "string(const string& s) -- 深拷貝" << endl;
        _str = new char[s._capacity + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
    }
    // 賦值重載
    string& operator=(const string& s)
    {
        cout << "string& operator=(string s) -- 深拷貝 " << endl;
        if (this != &s)
        {
            delete[] _str;
            _str = new char[s._capacity + 1];
            strcpy(_str, s._str);
            _size = s._size;
            _capacity = s._capacity;
        }
        return *this;
    }
    ~string()
    {
        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];
            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;
    }
private:
    char* _str;
    size_t _size;
    size_t _capacity; // 不包含最后做標(biāo)識(shí)的\0
};
string to_string(int value)
{
    bool flag = true;
    if (value < 0)
    {
        flag = false;
        value = 0 - value;
    }
    string str;
    while (value > 0)
    {
        int x = value % 10;
        value /= 10;
        str += ('0' + x);
    }
    if (flag == false)
    {
        str += '-';
    }
    std::reverse(str.begin(), str.end());
    return str;
}
}

運(yùn)行上述情況二的代碼可以看到:在過程中進(jìn)行了深拷貝,這里深拷貝的代價(jià)就非常的大,也就是左值引用的短板所在,因此提出了右值引用的概念。

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

使用右值引用和移動(dòng)語義解決上述問題:

在此之前,我們明確一個(gè)概念:在C++11中,對(duì)右值引用進(jìn)行了一個(gè)分類,將其分為純右值將亡值兩種,其中純右值指的是內(nèi)置類型表達(dá)式的值,將亡值是指自定義類型的表達(dá)式的值,所謂的將亡值也就是指生命周期將要結(jié)束的值,一般來說,匿名對(duì)象、臨時(shí)對(duì)象、move后的自定義類型對(duì)象都是將亡值。

讓我們思考一下,在上述情景過程中,進(jìn)行深拷貝之后,原來的局部變量str是會(huì)被析構(gòu)掉的,相當(dāng)于我們先構(gòu)造一個(gè)一摸一樣的變量,然后再析構(gòu)掉這個(gè)局部變量,那么我們不如直接將這個(gè)變量的資源交給另一個(gè)變量管理,這樣就能夠提高效率。

于是就有了移動(dòng)構(gòu)造這個(gè)接口:移動(dòng)構(gòu)造也是一個(gè)構(gòu)造函數(shù),它的參數(shù)是類型的右值引用,實(shí)際上就是把傳入右值的資源轉(zhuǎn)移過來,避免了深拷貝,所以稱為移動(dòng)構(gòu)造,就是移動(dòng)別人的資源來進(jìn)行構(gòu)造。

接下來我們來實(shí)現(xiàn)一下上面string的移動(dòng)構(gòu)造和移動(dòng)賦值

// 移動(dòng)構(gòu)造
string(string&& s)
    :_str(nullptr)
    , _size(0)
    , _capacity(0)
{
	cout << "string(string&& s) -- 移動(dòng)構(gòu)造" << endl;
	swap(s);
}
// 移動(dòng)賦值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移動(dòng)賦值" << endl;
    swap(s);
    return *this;
}

在我們自己實(shí)現(xiàn)的string中加入了這兩個(gè)函數(shù)之后,再次運(yùn)行剛剛的程序,就會(huì)發(fā)現(xiàn)函數(shù)調(diào)用改變了:深拷貝變成了移動(dòng)拷貝

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

下面,我們來分析一下為什么會(huì)變成這樣:

加入移動(dòng)語義之前:

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

加入移動(dòng)語義之后:

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

注:有種說法是右值引用延長了變量的生命周期,事實(shí)上這種說法是不準(zhǔn)確的,他只是將一個(gè)變量的資源轉(zhuǎn)移給了另一個(gè)變量,此變量本身的生命周期是沒有變化的。如果一定要這樣說的話,那可以理解成延長了這個(gè)資源的生命周期(但是資源沒有生命周期這一說)。

在C++11之后,STL容器中都增加了移動(dòng)構(gòu)造和移動(dòng)復(fù)制的接口

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

1.4 左值引用和右值引用的深入使用場(chǎng)景分析

根據(jù)上文,我們知道右值只能引用右值,但是右值一定不能引用左值嗎?

在有些場(chǎng)景下,我們可能真的需要右值去引用左值,從而實(shí)現(xiàn)移動(dòng)語義。

當(dāng)需要右值引用一個(gè)左值的時(shí)候,可以通過move函數(shù)將左值轉(zhuǎn)變?yōu)橛抑?/strong>。在C++11中,std::move()函數(shù)在<utility>頭文件中,這個(gè)函數(shù)只有一個(gè)唯一的作用就是將左值強(qiáng)制轉(zhuǎn)換成右值。

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

可以看到使用move將s1從左值變成右值之后,再次插入編譯器就將s1識(shí)別成將亡值,匹配到移動(dòng)構(gòu)造然后未查到lt中。

根據(jù)上述的例子我們知道庫里面的list是支持移動(dòng)構(gòu)造的,我們之前也模擬實(shí)現(xiàn)過list,那么現(xiàn)在能否對(duì)之前的list進(jìn)行一個(gè)改造,讓其也能支持移動(dòng)語義呢?

首先在這里附上之前實(shí)現(xiàn)的list的源碼

namespace zht
{
	template<class T>
	struct __list_node
	{
		__list_node* _prev;
		__list_node* _next;
		T _data;
		__list_node(const T& data = T())
			:_data(data)
			, _prev(nullptr)
			, _next(nullptr)
		{}
	};
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef __list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> Self;
		node* _pnode;

		__list_iterator(node* p)
			:_pnode(p)
		{}
		Ptr operator->()
		{
			return &operator*();
		}
		Ref operator*()
		{
			return _pnode->_data;
		}
		Self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}
		Self operator++(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}
		Self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}
		bool operator!=(const Self& it)
		{
			return _pnode != it._pnode;
		}
		bool operator==(const Self& it)
		{
			return _pnode == it._pnode;
		}
	};

	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_initialize()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		list()
		{
			empty_initialize();
		}
		list(size_t n, const T& val = T())
		{
			empty_initialize();
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_initialize();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list(const list<T>& lt)//經(jīng)典寫法
		{
		    empty_initialize();
		    for (const auto& e : lt)
		    {
		        push_back(e);
		    }
		}
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
		}
		list<T>& operator=(list<T>& lt)
		{
			if (this != *lt)
			{
				clear();
				for (auto& e : lt)
				{
					push_back(e);
				}
			}
		}
		bool empty()
		{
			return _head->_next == _head;
		}
		void clear()
		{
			while (!empty())
			{
				erase(--end());
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void push_back(const T& val = T())
		{
			insert(end(), val);
		}
		void push_front(const T& val = T())
		{
			insert(begin(), val);
		}
		iterator insert(iterator pos, const T& val = T())
		{
			node* newnode = new node(val);
			node* prev = pos._pnode->_prev;
			node* cur = pos._pnode;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			++_size;
			return iterator(newnode);
		}

		size_t size()
		{
			return _size;
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;
			prev->_next = next;
			next->_prev = prev;
			delete pos._pnode;
			--_size;
			return iterator(next);
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		void resize(size_t n, const T& val = T())
		{
			while (n < size())
			{
				pop_back();
			}
			while (n > size())
			{
				push_back(val);
			}
		}
	private:
		node* _head;
		size_t _size;
	};
}

使用我們自己實(shí)現(xiàn)的list執(zhí)行1.4中的Test6,將會(huì)得到如下結(jié)果:

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

為了讓我們的list也能像庫里面的list一樣,我們首先考慮到的就是實(shí)現(xiàn)push_back的右值引用版本,由于在push_back中調(diào)用了insert,所以insert也需要增加右值引用的版本,同樣的在node的構(gòu)造中也需要增加右值引用版本的構(gòu)造函數(shù),所以增加的函數(shù)如下:

//template<class T> struct __list_node
__list_node(T&& data)
    :_data(data)
    , _prev(nullptr)
    , _next(nullptr)
{}
//template<class T> class list
void push_back(T&& val)
{
    insert(end(), val);
}
iterator insert(iterator pos, T&& val)
{
    node* newnode = new node(val);
    node* prev = pos._pnode->_prev;
    node* cur = pos._pnode;
    prev->_next = newnode;
    newnode->_prev = prev;
    newnode->_next = cur;
    cur->_prev = newnode;
    ++_size;
    return iterator(newnode);
}

那么現(xiàn)在再來嘗試執(zhí)行以下,發(fā)現(xiàn)沒有變化,這是為什么呢?

(這里大家可以自己去實(shí)踐一下)通過調(diào)試可以發(fā)現(xiàn)確實(shí)調(diào)用了右值引用版本的push_back,但是繼續(xù)往下面調(diào)試就發(fā)現(xiàn)調(diào)用的是左值版本的insert,這是什么原因呢??

因?yàn)橛抑狄弥笞兞勘旧硎亲笾?,所以這里val的屬性就是一個(gè)左值,所以當(dāng)然會(huì)匹配到左值版本的insert,所以這里在傳參的時(shí)候需要將這個(gè)val的屬性變成右值,這里可以使用move來改一下傳參之后的屬性,然后再編譯運(yùn)行,發(fā)現(xiàn)還是和原來的結(jié)果一樣,這是因?yàn)楹瘮?shù)套函數(shù),每一層都需要將參數(shù)屬性改為右值,非常的麻煩,如果我們將所有的參數(shù)都用move修改一下之后:

__list_node(T&& data)
    :_data(move(data))
    , _prev(nullptr)
    , _next(nullptr)
{}		
void push_back(T&& val)
{
    insert(end(), move(val));
}
iterator insert(iterator pos, T&& val)
{
    node* newnode = new node(move(val));
    node* prev = pos._pnode->_prev;
    node* cur = pos._pnode;
    prev->_next = newnode;
    newnode->_prev = prev;
    newnode->_next = cur;
    cur->_prev = newnode;
    ++_size;
    return iterator(newnode);
}

再次運(yùn)行之前的代碼,可以看到已經(jīng)實(shí)現(xiàn)了庫里面的效果,這里多了一個(gè)string的構(gòu)造和移動(dòng)構(gòu)造是因?yàn)槌跏蓟痩t的時(shí)候調(diào)用的,由于我們string和list的實(shí)現(xiàn)和庫里面還是有些許不同點(diǎn)的。

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

至此,我們就完成了自己的list中對(duì)移動(dòng)語義的支持了。

1.5 完美轉(zhuǎn)發(fā)

1.5.1 萬能引用

我們上面都是單獨(dú)定義一個(gè)參數(shù)為右值引用的函數(shù),然后讓編譯器根據(jù)實(shí)參的類型來選擇調(diào)用參數(shù)為左值引用的構(gòu)造/插入接口還是參數(shù)為右值引用的構(gòu)造/插入接口。那么,我們能不能讓函數(shù)能夠根據(jù)實(shí)參的類型自動(dòng)實(shí)例化出對(duì)應(yīng)不同的函數(shù)呢?

萬能引用可以實(shí)現(xiàn)這個(gè)功能。

所謂的萬能引用,實(shí)際上是一個(gè)模板,且函數(shù)的形參為右值引用。對(duì)于這種模板,編譯器能夠根據(jù)實(shí)參的類型自動(dòng)推衍實(shí)例化出不同的形參類型,這個(gè)模板能夠接收左值/const左值/右值/const右值,推衍出的類型為:左值引用/const左值引用/右值引用/const右值引用。

舉個(gè)例子

template<class T>
void PerfectForward(T&& x)
{
    cout << "void PerfectForward(int&& x)" << endl;
}
void Test7()
{
    PerfectForward(10); // 右值
    int a = 10;
    PerfectForward(a); //左值
    const int b = 8;
    PerfectForward(b); //const 左值
    PerfectForward(std::move(b)); //const右值
}

四個(gè)變量同時(shí)調(diào)用PerfectForward,都能夠調(diào),不會(huì)報(bào)錯(cuò)。

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

這里提一下,如果這里的四個(gè)調(diào)用全部都出現(xiàn)了,那么其中的x就不能改變

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

?但是如果將后面兩個(gè)const調(diào)用的語句屏蔽掉就能夠編譯成功,這是什么原因呢?

?我們寫的PerfectForward是一個(gè)函數(shù)模板,在編譯運(yùn)行的過程中會(huì)實(shí)例化出來四個(gè)不同的函數(shù),由于調(diào)用傳參的過程中有const修飾的形參被實(shí)例化,所以實(shí)例化后的此函數(shù)就會(huì)報(bào)錯(cuò)。

1.5.2 完美轉(zhuǎn)發(fā)

接下來我們將上述的代碼進(jìn)行一點(diǎn)點(diǎn)更改

void Func(int& x)
{
    cout << "lvalue reference" << endl;
}
void Func(int&& x)
{
    cout << "rvalue reference " << endl;
}
void Func(const int& x)
{
    cout << "const lvalue reference " << endl;
}
void Func(const int&& x)
{
    cout << "const rvalue reference " << endl;
}
template<class T>
void PerfectForward(T&& x)
{
    Func(x);
}
void Test7()
{
    PerfectForward(10); // 右值
    int a = 10;
    PerfectForward(a); //左值
    const int b = 8;
    PerfectForward(b); //const 左值
    PerfectForward(std::move(b)); //const右值
}

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

運(yùn)行這個(gè)代碼我們發(fā)現(xiàn),不管是左值引用還是右值引用,只能調(diào)用到左值版本的Func函數(shù),原因在上文中已經(jīng)說過了:右值引用之后變量本身是左值,所以只能調(diào)用到左值版本的Func,那么如何能讓左值的調(diào)用左值版本,右值的調(diào)用右值版本呢?

使用完美轉(zhuǎn)發(fā)std::forward,std::forward 完美轉(zhuǎn)發(fā)在傳參的過程中保留對(duì)象原生類型屬性

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

2. 新的類功能

2.1 默認(rèn)成員函數(shù)

在之前的文章【C++】類和對(duì)象中我們講到類的默認(rèn)成員函數(shù)一共有六個(gè):

  • 構(gòu)造函數(shù)
  • 析構(gòu)函數(shù)
  • 拷貝構(gòu)造函數(shù)
  • 賦值重載函數(shù)
  • 取地址重載
  • const取地址重載

其中重要的是前4個(gè),后兩個(gè)用處不大,但是在C++11中新增了兩個(gè)默認(rèn)成員函數(shù):移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值重載函數(shù),這就與我們上文中講到的對(duì)應(yīng)起來了。

同樣的這兩個(gè)函數(shù)也有一些比較難搞的特性:

1. 移動(dòng)構(gòu)造函數(shù)

編譯器自動(dòng)生成的條件:

  1. 沒有自己實(shí)現(xiàn)移動(dòng)構(gòu)造;
  2. 沒有實(shí)現(xiàn)析構(gòu)函數(shù)、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè)

自動(dòng)生成的移動(dòng)構(gòu)造的特性:

  • 對(duì)于內(nèi)置類型,將會(huì)逐成員按字節(jié)進(jìn)行拷貝
  • 對(duì)于自定義類型,如果這個(gè)類型成員有移動(dòng)構(gòu)造就調(diào)用移動(dòng)構(gòu)造,否則就調(diào)用拷貝構(gòu)造

2. 移動(dòng)賦值重載函數(shù)

編譯器自動(dòng)生成的條件

  1. 沒有自己實(shí)現(xiàn)移動(dòng)賦值重載
  2. 沒有實(shí)現(xiàn)析構(gòu)函數(shù)、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè)

自動(dòng)生成的移動(dòng)賦值重載的特性:

  • 對(duì)于內(nèi)置類型,將會(huì)逐成員按字節(jié)進(jìn)行拷貝
  • 對(duì)于自定義類型,如果這個(gè)類型成員有移動(dòng)賦值就調(diào)用移動(dòng)賦值,否則就調(diào)用移動(dòng)賦值

我們看下面一段代碼:

class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name)
        , _age(age)
    {}
private:
    zht::string _name;
    int _age;
};
void Test8()
{
    Person p1;
    Person p2 = p1;
    Person p3 = std::move(p1);
    Person p4;
    p4 = std::move(p2);
}

運(yùn)行結(jié)果如下:

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

分析一下這個(gè)結(jié)果:

首先可以看到的是Person這個(gè)類沒有實(shí)現(xiàn)移動(dòng)構(gòu)造,并且沒有實(shí)現(xiàn)析構(gòu)函數(shù)、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè),所以編譯器自動(dòng)生成了移動(dòng)構(gòu)造和其他的默認(rèn)構(gòu)造函數(shù)。

對(duì)于第15行內(nèi)容,由于p1是左值,所以匹配到拷貝構(gòu)造,Person中默認(rèn)生成的拷貝構(gòu)造會(huì)調(diào)用zht::string中的拷貝構(gòu)造,因此輸出string(const string& s) -- 深拷貝,對(duì)于第16行,將p1move之后,變成了右值,因此調(diào)用Person自動(dòng)生成的移動(dòng)構(gòu)造,這個(gè)移動(dòng)構(gòu)造將會(huì)調(diào)用zht::string中的移動(dòng)構(gòu)造,因此輸出string(string&& s) -- 移動(dòng)構(gòu)造,對(duì)于18行,p2move之后變成右值,調(diào)用Person自動(dòng)生成的移動(dòng)賦值,此函數(shù)調(diào)用zht::string中的移動(dòng)賦值,因此輸出string& operator=(string&& s) -- 移動(dòng)賦值

2.2 類成員變量初始化

C++11允許類定義的時(shí)候給定成員變量的初始缺省值,默認(rèn)生成的構(gòu)造函數(shù)在初始化列表的時(shí)候?qū)?huì)使用這些缺省值初始化。

看下面一段代碼:

class Date1
{
public:
    int _year;
    int _month;
    int _day;
};
class Date2
{
public:
    int _year = 1970;
    int _month = 1;
    int _day = 1;
};

void Test9()
{
    Date1 d11;
    Date2 d12;
}

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

在調(diào)試過程看到:Date1對(duì)應(yīng)的對(duì)象d11中的成員變量都沒有被初始化,還是隨機(jī)值,但是Date2對(duì)應(yīng)的對(duì)象d12被默認(rèn)初始化成了缺省值。

2.3 強(qiáng)制生成默認(rèn)函數(shù)的關(guān)鍵字defaule

在2.1中我們講到移動(dòng)構(gòu)造和移動(dòng)賦值的默認(rèn)生成條件比較苛刻。假設(shè)我們已經(jīng)寫了拷貝構(gòu)造,就不會(huì)生成移動(dòng)構(gòu)造了,但是我們希望移動(dòng)構(gòu)造能夠被自動(dòng)生成,就可以使用default關(guān)鍵字顯示指定移動(dòng)構(gòu)造生成。

class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name)
        , _age(age)
    {}
    Person(const Person& p)
        :_name(p._name)
        ,_age(p._age)
    {}
    //這里使用default顯示指定移動(dòng)構(gòu)造和移動(dòng)賦值生成
    Person(Person&& p) = default;
    Person& operator=(Person&& p) = default;

    Person& operator=(const Person& p)
    {
        if(this != &p)
        {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }
    ~Person()
    {}
private:
    zht::string _name;
    int _age;
};
void Test8()
{
    Person p1;
    Person p2 = p1;
    Person p3 = std::move(p1);
    Person p4;
    p4 = std::move(p2);
}

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

可以看見此時(shí)已經(jīng)手動(dòng)寫了拷貝構(gòu)造和拷貝賦值重載,但是還是生成了移動(dòng)構(gòu)造和移動(dòng)賦值重載。

2.4 禁止生成默認(rèn)函數(shù)的關(guān)鍵字delete

如果想要限制某些默認(rèn)成員函數(shù)的生成或者使用,在C++98中的做法是:將該函數(shù)設(shè)置成private,并且只生成不定義,這樣在類外調(diào)用的時(shí)候就會(huì)報(bào)錯(cuò)。

在C++11中的做法就更加簡(jiǎn)單了:只需要在該函數(shù)的聲明中加上=delete即可,該語法指示編譯器不生成對(duì)應(yīng)函數(shù)的默認(rèn)版本,稱=delete修飾的函數(shù)為刪除函數(shù)。

class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name)
        , _age(age)
    {}
    Person(const Person& p) = delete;
private:
    zht::string _name;
    int _age;
};
void Test8()
{
    Person p1;
    Person p2 = p1;
    Person p3 = std::move(p1);
    Person p4;
    p4 = std::move(p2);
}

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

此時(shí)調(diào)用拷貝構(gòu)造就會(huì)出現(xiàn)報(bào)錯(cuò):嘗試引用已刪除的函數(shù)

2.5繼承和多態(tài)中的final與override關(guān)鍵字

關(guān)于final和override關(guān)鍵字,在之前的博客中已經(jīng)講解,這里就不在贅述,有需要的小伙伴可以去看一下

【C++】多態(tài)

3. 可變參數(shù)模版

我們之前了解到模板的概念,讓我們的代碼中類和函數(shù)都可以模板化,從而支持多種不同類型。但是在C++98/03中,類模板和函數(shù)模板的參數(shù)只能是固定數(shù)量的,但是在C++11中,出現(xiàn)了可變模板參數(shù),讓模板參數(shù)能夠接收不同數(shù)量的參數(shù)。

關(guān)于可變參數(shù)模板,這里只學(xué)習(xí)一些基本的特性,了解即可。想要深入了解的小伙伴可以自行查找資料。

3.1 可變參數(shù)模板的語法

對(duì)于可變參數(shù),其實(shí)在我們剛開始學(xué)習(xí)C語言的時(shí)候就已經(jīng)使用可變參數(shù)的函數(shù)了,對(duì),就是printf函數(shù)

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

看到printf函數(shù)原型中用...表示可變參數(shù),C++11也采用了類似的方法,我們看下面一個(gè)C++的可變參數(shù)的函數(shù)模板

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

注意:這里的Args是一個(gè)模板參數(shù)包,args是一個(gè)函數(shù)形參的參數(shù)包,這個(gè)參數(shù)包中可以包含0-N個(gè)參數(shù)

上面的參數(shù)args前面有省略號(hào),所以它就是一個(gè)可變模版參數(shù),我們把帶省略號(hào)的參數(shù)稱為“參數(shù)包”,它里面包含了0到N(N>=0)個(gè)模版參數(shù)。

獲取參數(shù)包中參數(shù)個(gè)數(shù)的方式只有一種:使用sizeof關(guān)鍵字

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

這里有一個(gè)點(diǎn)需要注意:我們需要將代表參數(shù)包的...放在sizeof的括號(hào)外面,不要思考這個(gè)用法的邏輯,當(dāng)作新的語法記住就好。

但是這里有一個(gè)問題,我們無法直接獲取參數(shù)包args中的每個(gè)參數(shù)的,只能通過展開參數(shù)包的方式來獲取參數(shù)包中的每個(gè)參數(shù),這是使用可變模版參數(shù)的一個(gè)主要特點(diǎn),也是最大的難點(diǎn),即如何展開可變模版參數(shù)。由于語法不支持使用args[i]這樣方式獲取可變參數(shù),所以我們的用一些奇招來獲取參數(shù)包的值。

3.2 遞歸函數(shù)方式展開參數(shù)包

這里我們主要利用的就是參數(shù)包里的參數(shù)個(gè)數(shù)可以是任意個(gè),所以設(shè)計(jì)一個(gè)重載的函數(shù)用來當(dāng)作遞歸的出口

//遞歸出口
template<class T>
void ShowList(const T& t)
{
    cout << t << endl;
}
//遞歸過程
template<class T, class ...Args>
void ShowList(T value, Args... args)
{
    cout << value << " ";
    ShowList(args...);//這里要使用...表示將參數(shù)包展開
}
void Test10()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', string("sort"));
}

這里如果參數(shù)包里之后一個(gè)參數(shù)的話,就調(diào)用遞歸出口,如果大于一個(gè)參數(shù),那么就會(huì)調(diào)用遞歸過程,然后將第一個(gè)參數(shù)識(shí)別給T,剩下的參數(shù)都放進(jìn)從參數(shù)包中,遞歸調(diào)用。

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

3.3 逗號(hào)表達(dá)式展開參數(shù)包

template<class T>
void PrintArgs(T t)
{
    cout << t << " ";
}
template<class... Args>
void ShowList(Args... args)
{
    int arr[] = { (PrintArgs(args), 0)... };
    cout << endl;
}
void Test10()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', string("sort"));
}

這種展開參數(shù)包的方式,不需要通過遞歸終止函數(shù),是直接在expand函數(shù)體中展開的, PrintArg不是一個(gè)遞歸終止函數(shù),只是一個(gè)處理參數(shù)包中每一個(gè)參數(shù)的函數(shù)。這種就地展開參數(shù)包的方式實(shí)現(xiàn)的關(guān)鍵是逗號(hào)表達(dá)式。我們知道逗號(hào)表達(dá)式會(huì)按順序執(zhí)行逗號(hào)前面的表達(dá)式。
expand函數(shù)中的逗號(hào)表達(dá)式:(printarg(args), 0),也是按照這個(gè)執(zhí)行順序,先執(zhí)行PrintArg(args),再得到逗號(hào)表達(dá)式的結(jié)果0。

同時(shí)還用到了C++11的另外一個(gè)特性——初始化列表,通過初始化列表來初始化一個(gè)變長數(shù)組, {(printarg(args), 0)…}將會(huì)展開成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ),最終會(huì)創(chuàng)建一個(gè)元素值都為0的數(shù)組int arr[sizeof…(Args)]。由于是逗號(hào)表達(dá)式,在創(chuàng)建數(shù)組的過程中會(huì)先執(zhí)行逗號(hào)表達(dá)式前面的部分printarg(args)打印出參數(shù),也就是說在構(gòu)造int數(shù)組的過程中就將參數(shù)包展開了,這個(gè)數(shù)組的目的純粹是為了在數(shù)組構(gòu)造的過程展開參數(shù)包。

3.4 可變參數(shù)模板在STL中的應(yīng)用——empalce相關(guān)接口函數(shù)

在增加了可變參數(shù)模板的語法之后,STL也增加了對(duì)應(yīng)的接口,這里我們看一下vector中的emplace。

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

首先我們看到的emplace系列的接口,支持模板的可變參數(shù),并且萬能引用。那么相對(duì)insert和emplace系列接口的優(yōu)勢(shì)到底在哪里呢?

  • 對(duì)于內(nèi)置類型來說,emplace 接口和傳統(tǒng)的插入接口在效率上是沒有區(qū)別的,因?yàn)閮?nèi)置類型是直接插入的,不需要進(jìn)行拷貝構(gòu)造;

  • 對(duì)于需要進(jìn)行深拷貝的自定義類型來說,如果該類實(shí)現(xiàn)了移動(dòng)構(gòu)造,則 emplace 接口會(huì)比傳統(tǒng)插入接口少一次淺拷貝,但總體效率差不多;如果該類沒有實(shí)現(xiàn)移動(dòng)構(gòu)造,則 emplace 接口的插入效率要遠(yuǎn)高于傳統(tǒng)插入接口;

  • 這是因?yàn)樵趥鹘y(tǒng)的插入接口中,需要先創(chuàng)建一個(gè)臨時(shí)對(duì)象,然后將這個(gè)對(duì)象深拷貝或者移動(dòng)拷貝到容器中,而 std::emplace() 則通過使用可變參數(shù)模板、萬能模板等技術(shù),直接在容器中構(gòu)造對(duì)象,避免了對(duì)象的拷貝和移動(dòng);

  • 對(duì)于不需要進(jìn)行深拷貝的自定義類型來說,emplace 接口也會(huì)比傳統(tǒng)插入接口少一次淺拷貝 (拷貝構(gòu)造),但總體效率也差不多;原因和上面一樣,emplace 接口可以直接在容器中原地構(gòu)造新的對(duì)象,避免了不必要的拷貝過程

在上一篇文中我們講到,emplace 接口要比傳統(tǒng)的插入接口高效,我們能使用 emplace 就不要使用傳統(tǒng)插入接口,嚴(yán)格意義上說這種說法沒有問題,但是并不是絕對(duì)的;因?yàn)?STL 中的容器都支持移動(dòng)構(gòu)造,所以 emplace 接口僅僅是少了一次淺拷貝而已,而淺拷貝的代價(jià)并不大;所以我們?cè)谑褂?STL 容器時(shí)并不需要去刻意的使用 emplace 系列接口。

注意:上面的傳統(tǒng)接口的移動(dòng)構(gòu)造和 emplace 接口的直接在容器中構(gòu)造對(duì)象都只針對(duì)右值 (將亡值),而對(duì)于左值,它都只能老實(shí)的進(jìn)行深拷貝。

4. lambda表達(dá)式

在C++11之前,我們想要使用sort排序,使用sort,需要傳仿函數(shù)用于規(guī)定比較的原則

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

可以看到,如果想要排序內(nèi)置類型,可以使用仿函數(shù)greater/less,但是如果要排序自定義類型,就得自己寫仿函數(shù)。特別是如果遇到同一個(gè)類按照不同方式排序的情況,就要去實(shí)現(xiàn)不同的類,特別是相同類的命名,給開發(fā)者帶來了極大的不便,因此C++11中引進(jìn)了Lambda表達(dá)式

首先我們見一見什么叫l(wèi)ambda表達(dá)式

struct Goods
{
    string _name; // 名字
    double _price; // 價(jià)格
    int _evaluate; // 評(píng)價(jià)
    Goods(const char* str, double price, int evaluate)
        :_name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};
void Test12()
{
    //這里想對(duì)存放的Goods對(duì)象按照不同的方式進(jìn)行排序,就可以使用lambda表達(dá)式
    vector<Goods> v = { {"apple",2.1,5},{"banana",3,4},{"orange",2.2,3}, {"pineapple",1.5,4 } };
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._name < g2._name; });
    cout << "sort by name" << endl;
    for (auto& e : v)
    {
        cout << e._name << " " << e._price << " " << e._evaluate << endl;
    }
    cout << endl;
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price; });
    cout << "sort by price" << endl;
    for (const auto& e : v)
    {
        std::cout << e._name << " " << e._price << " " << e._evaluate << endl;
    }
    cout << endl;
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate < g2._evaluate; });
    cout << "sort by evaluate" << endl;
    for (const auto& e : v)
    {
        cout << e._name << " " << e._price << " " << e._evaluate << endl;
    }
    cout << endl;
}

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

4.1 Lambda表達(dá)式的語法與用法

lambda表達(dá)式書寫格式

[capture-list] (parameters) mutable -> return-type { statement}

表達(dá)式說明:

  • [capture-list]捕捉列表。出現(xiàn)在lambda函數(shù)的開始位置,編譯器根據(jù)[]來判斷接下來的代碼是否是lambda表達(dá)式,因此此項(xiàng)不能省略,捕捉列表能夠捕捉上下文中的變量共lambda表達(dá)式使用。

  • (parameters):參數(shù)列表。與普通函數(shù)的參數(shù)列表一致,如果不需要參數(shù)傳遞,可以連同()一起省略。

  • mutable:默認(rèn)情況下,lambda表達(dá)式(函數(shù))總是一個(gè)const函數(shù),mutable可以取消其常性。

    注:使用此修飾符的時(shí)候,參數(shù)列表不可省略

  • ->return-type:返回值類型。用追蹤返回類型形式聲明函數(shù)的返回值類型,沒有返回值時(shí)此部分可以省略。返回值類型明確的情況下也可以省略,由編譯器對(duì)返回類型進(jìn)行推導(dǎo)。

  • {statement}:函數(shù)體。在函數(shù)體內(nèi)部,除了可以使用(parameters)中的函數(shù)參數(shù)外,還可以使用[capture-list]捕捉到的變量。

根據(jù)上述的語法格式,我們知道參數(shù)列表和返回值類型都是可選部分,而捕捉列表和函數(shù)體可以為空

void Test13()
{
    // 最簡(jiǎn)單的lambda表達(dá)式,沒有任何實(shí)際意義
    [] {};

    // 省略參數(shù)列表和返回值類型,返回值類型由編譯器推導(dǎo)為int
    int a = 3, b = 4;
    [=] {return a + 3; };

    // 省略了返回值類型,無返回值類型
    auto fun1 = [&](int c) {b = a + c; };//由于lambda表達(dá)式的類型是編譯器自動(dòng)生成的,非常復(fù)雜,所有我們使用auto來定義
    fun1(10);
    cout << a << " " << b << endl;
    
    // 各部分都很完善的lambda函數(shù)
    auto fun2 = [=, &b](int c)->int {return b += a + c; };
    cout << fun2(10) << endl;

    // 賦值捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable {
        x *= 2;
        return a + x; };
    cout << add_x(10) << endl;
}

捕捉列表說明

捕捉列表描述了上下文中哪些數(shù)據(jù)可以被lambda使用,以及使用的方式傳值還是傳引用

  • [var]:表示傳值的方式捕捉變量var
  • [=]:表示傳值的方式捕捉所有父作用域的變量(包括this)
  • [&var]:表示引用捕捉變量var
  • [&]:表示引用捕捉所有父作用域的變量(包括this)
  • [this]:標(biāo)志值傳遞的方式捕捉當(dāng)前的this指針

注意

  1. 父作用域指包含lambda函數(shù)的語句塊

  2. 語法上捕捉列表可以由多個(gè)捕捉項(xiàng)組成,并以逗號(hào)分隔

    例如:[=, &a, &b]:表示以引用傳遞的放啊是捕捉a和b,值傳遞方式捕捉其他所有變量

    ? [&, a, this]:值傳遞的方式捕捉a和this,引用方式捕捉其他所有變量

  3. 捕捉列表不允許變量重復(fù)傳遞,否則就會(huì)導(dǎo)致編譯錯(cuò)誤

    在第2點(diǎn)上我們見到了[=, &a]這種用法,默認(rèn)按照值傳遞的方式捕捉了所有的變量,但是把a(bǔ)單獨(dú)拿出來說,意思將a單獨(dú)處理,按照引用的方式傳遞。但是如果這里換成[=, a],就出現(xiàn)了重復(fù)傳遞,會(huì)導(dǎo)致編譯錯(cuò)誤。

  4. 在塊作用域以外的lambda函數(shù)捕捉列表必須為空

  5. 在塊作用域中的lambda函數(shù)僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會(huì)導(dǎo)致編譯報(bào)錯(cuò)

  6. lambda表達(dá)式之間不能相互賦值,及時(shí)看起來類型相同

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

4.2 Lambda表達(dá)式的底層原理

實(shí)際上編譯器在底層對(duì)lambda表達(dá)式的處理方式,是轉(zhuǎn)換成函數(shù)對(duì)象(仿函數(shù))再處理的。所謂仿函數(shù),就是在類中重載了operator()運(yùn)算符。我們看下面一段代碼

class Add
{
public:
    Add(int base)
        :_base(base)
    {}
    int operator()(int num)
    {
        return _base + num;
    }
private:
    int _base;
};
void Test15()
{
    int base = 1;

    //仿函數(shù)的調(diào)用方式
    Add add1(base);//構(gòu)造一個(gè)函數(shù)對(duì)象
    cout << add1(10) << endl;

    //lambda表達(dá)式
    auto add2 = [base](int num)->int 
    {
        return base + num; 
    };
    cout << add2(10) << endl;
}

這里定義了一個(gè)Add類,在其中進(jìn)行了operator()的重載,然后實(shí)例化出了add1就可以叫做函數(shù)對(duì)象,可以像函數(shù)一樣使用。然后定義了add2,這里將lambda表達(dá)式賦值給add2,此時(shí)add1和add2都可以像函數(shù)一樣使用。

接下來我們看一下匯編的情況

【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式,C++,c++,開發(fā)語言,C++11

我們首先看匯編語言中的1,可以看到在使用函數(shù)對(duì)象的時(shí)候,調(diào)用了Add中的operator()。

看2,這里是使用lambda表達(dá)式對(duì)add2進(jìn)行賦值和調(diào)用,可以看到調(diào)用的同樣也是operator()函數(shù)。值得注意的是這里調(diào)用的是<lambda_1>中的operator()本質(zhì)就是lambda表達(dá)式在底層被轉(zhuǎn)換成了仿函數(shù)。

?為什么我們不能顯示的寫lambda表達(dá)式的類型?

?因?yàn)檫@個(gè)類型是由編譯器自動(dòng)生成的,我們沒辦法知道這個(gè)類名稱的具體寫法。

在VS下,生成的這類的類名叫做lambda_uuid,類名中的uuid叫做通用唯一識(shí)別碼(Universally Unique Identifier),簡(jiǎn)單來說,uuid就是通過算法生成一串字符串,保證在當(dāng)前程序當(dāng)中每次生成的uuid都不會(huì)重復(fù),這樣就能保證每個(gè)lambda表達(dá)式底層類名都是唯一的。

(img-indD9wQt-1690376726287)]

4.2 Lambda表達(dá)式的底層原理

實(shí)際上編譯器在底層對(duì)lambda表達(dá)式的處理方式,是轉(zhuǎn)換成函數(shù)對(duì)象(仿函數(shù))再處理的。所謂仿函數(shù),就是在類中重載了operator()運(yùn)算符。我們看下面一段代碼

class Add
{
public:
    Add(int base)
        :_base(base)
    {}
    int operator()(int num)
    {
        return _base + num;
    }
private:
    int _base;
};
void Test15()
{
    int base = 1;

    //仿函數(shù)的調(diào)用方式
    Add add1(base);//構(gòu)造一個(gè)函數(shù)對(duì)象
    cout << add1(10) << endl;

    //lambda表達(dá)式
    auto add2 = [base](int num)->int 
    {
        return base + num; 
    };
    cout << add2(10) << endl;
}

這里定義了一個(gè)Add類,在其中進(jìn)行了operator()的重載,然后實(shí)例化出了add1就可以叫做函數(shù)對(duì)象,可以像函數(shù)一樣使用。然后定義了add2,這里將lambda表達(dá)式賦值給add2,此時(shí)add1和add2都可以像函數(shù)一樣使用。

接下來我們看一下匯編的情況

[外鏈圖片轉(zhuǎn)存中…(img-LEIRNSC7-1690376726288)]

我們首先看匯編語言中的1,可以看到在使用函數(shù)對(duì)象的時(shí)候,調(diào)用了Add中的operator()。

看2,這里是使用lambda表達(dá)式對(duì)add2進(jìn)行賦值和調(diào)用,可以看到調(diào)用的同樣也是operator()函數(shù)。值得注意的是這里調(diào)用的是<lambda_1>中的operator(),本質(zhì)就是lambda表達(dá)式在底層被轉(zhuǎn)換成了仿函數(shù)。

?為什么我們不能顯示的寫lambda表達(dá)式的類型?

?因?yàn)檫@個(gè)類型是由編譯器自動(dòng)生成的,我們沒辦法知道這個(gè)類名稱的具體寫法。

在VS下,生成的這類的類名叫做lambda_uuid,類名中的uuid叫做通用唯一識(shí)別碼(Universally Unique Identifier),簡(jiǎn)單來說,uuid就是通過算法生成一串字符串,保證在當(dāng)前程序當(dāng)中每次生成的uuid都不會(huì)重復(fù),這樣就能保證每個(gè)lambda表達(dá)式底層類名都是唯一的。


本節(jié)完 …文章來源地址http://www.zghlxwxcb.cn/news/detail-609782.html

到了這里,關(guān)于【C++】C++11右值引用|新增默認(rèn)成員函數(shù)|可變參數(shù)模版|lambda表達(dá)式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【C++干貨鋪】C++11新特性——右值引用、移動(dòng)構(gòu)造、完美轉(zhuǎn)發(fā)

    【C++干貨鋪】C++11新特性——右值引用、移動(dòng)構(gòu)造、完美轉(zhuǎn)發(fā)

    ========================================================================= 個(gè)人主頁點(diǎn)擊直達(dá):小白不是程序媛 C++系列專欄:C++干貨鋪 代碼倉庫:Gitee ========================================================================= 目錄 左值與左值引用 右值與右值引用 左值引用和右值引用的比較 ?左值引用總結(jié):

    2024年01月25日
    瀏覽(19)
  • 【C++】 C++11(右值引用,移動(dòng)語義,bind,包裝器,lambda,線程庫)

    【C++】 C++11(右值引用,移動(dòng)語義,bind,包裝器,lambda,線程庫)

    C++11是C++語言的一次重大更新版本,于2011年發(fā)布,它包含了一些非常有用的新特性,為開發(fā)人員提供了更好的編程工具和更好的編程體驗(yàn),使得編寫高效、可維護(hù)、安全的代碼更加容易。 一些C++11的新增特性包括: 強(qiáng)制類型枚舉,使得枚舉類型的通常行為更加可靠和容易控制

    2024年02月10日
    瀏覽(20)
  • 再探C++——默認(rèn)成員函數(shù)

    再探C++——默認(rèn)成員函數(shù)

    目錄 一、構(gòu)造函數(shù) 二、析構(gòu)函數(shù) 三、賦值運(yùn)算符 四、拷貝構(gòu)造 如果一個(gè)類中沒有成員,我們稱為空類??疹悾泊嬖?個(gè)默認(rèn)的類成員函數(shù)。 默認(rèn)成員函數(shù):用戶不顯示地寫,編譯器會(huì) 默認(rèn)生成 的函數(shù)叫做默認(rèn)成員函數(shù)。 6個(gè)默認(rèn)成員函數(shù): 構(gòu)造函數(shù):完成對(duì)象初始化?

    2024年02月14日
    瀏覽(19)
  • 【C++】類的默認(rèn)成員函數(shù)----const成員函數(shù)(超詳細(xì)解析)

    【C++】類的默認(rèn)成員函數(shù)----const成員函數(shù)(超詳細(xì)解析)

    目錄 一、前言 二、const成員函數(shù)? ??const修飾類的成員函數(shù)? ??問題1? ??問題2 ??針對(duì)const成員函數(shù)的??济嬖囶}(重點(diǎn)?。。???取地址及const取地址操作符重載 三、共勉 ?? 在我們前面學(xué)習(xí)的 類 中,我們會(huì)定義 成員變量 和 成員函數(shù) ,這些我們自己定義的函數(shù)都是普

    2024年04月14日
    瀏覽(17)
  • 【C++】C++11可變參數(shù)模板

    【C++】C++11可變參數(shù)模板

    ?? 樊梓慕: 個(gè)人主頁 ??? 個(gè)人專欄: 《C語言》 《數(shù)據(jù)結(jié)構(gòu)》 《藍(lán)橋杯試題》 《LeetCode刷題筆記》 《實(shí)訓(xùn)項(xiàng)目》 《C++》 《Linux》 《算法》 ?? 每一個(gè)不曾起舞的日子,都是對(duì)生命的辜負(fù) 目錄 前言 可變參數(shù)模板的定義方式 可變參數(shù)模板的使用? 編譯時(shí)遞歸展開參數(shù)包

    2024年04月10日
    瀏覽(24)
  • C++類的默認(rèn)成員函數(shù)

    C++類的默認(rèn)成員函數(shù)

    什么是默認(rèn)函數(shù)? 默認(rèn)函數(shù)就是當(dāng)你使用這個(gè)類對(duì)象時(shí),這個(gè)類會(huì)自動(dòng)調(diào)用的函數(shù)C++中有六個(gè)默認(rèn)成員函數(shù),并且作用各不相同,下面我們來一一進(jìn)行介紹 什么是構(gòu)造函數(shù)?構(gòu)造函數(shù)是干什么的? 什么是析構(gòu)函數(shù)?析構(gòu)函數(shù)是干什么的? 我們以棧為例,每一次我們?cè)谑褂脳5臅r(shí)

    2024年02月02日
    瀏覽(23)
  • [C++]六大默認(rèn)成員函數(shù)詳解

    [C++]六大默認(rèn)成員函數(shù)詳解

    ??個(gè)人主頁:fighting小澤 ??作者簡(jiǎn)介:目前正在學(xué)習(xí)C++和Linux ??博客專欄:C++入門 ???歡迎關(guān)注:評(píng)論????點(diǎn)贊????留言???? 如果一個(gè)類中什么都沒有,簡(jiǎn)稱空類。 但它并不是什么都沒有,任何類在什么都不寫的情況下, 編譯器會(huì)自動(dòng)生成以下6個(gè)默認(rèn)成員函數(shù)。

    2024年02月04日
    瀏覽(27)
  • 【C++】C++11語法 ~ 可變參數(shù)模板

    【C++】C++11語法 ~ 可變參數(shù)模板

    (???(??? )??,我是 Scort 目前狀態(tài):大三非科班啃C++中 ??博客主頁:張小姐的貓~江湖背景 快上車??,握好方向盤跟我有一起打天下嘞! 送給自己的一句雞湯??: ??真正的大師永遠(yuǎn)懷著一顆學(xué)徒的心 作者水平很有限,如果發(fā)現(xiàn)錯(cuò)誤,可在評(píng)論區(qū)指正,感謝?? ????

    2024年02月19日
    瀏覽(42)
  • C++ 11新特性之可變參數(shù)模板

    概述 ????????隨著C++ 11標(biāo)準(zhǔn)的發(fā)布,C++語言獲得了許多強(qiáng)大的新特性,其中一項(xiàng)顯著提升靈活性和實(shí)用性的創(chuàng)新便是可變參數(shù)模板。這一特性極大地?cái)U(kuò)展了模板在處理不定數(shù)量類型或值參數(shù)時(shí)的能力,為開發(fā)者提供了更為強(qiáng)大且靈活的泛型編程工具。 工作機(jī)制 ??????

    2024年02月22日
    瀏覽(26)
  • 【C++】類的默認(rèn)成員函數(shù)(下)

    【C++】類的默認(rèn)成員函數(shù)(下)

    ?? 博客主頁 : 小羊失眠啦. ?? 系列專欄 : 《C語言》 《數(shù)據(jù)結(jié)構(gòu)》 《C++》 《Linux》 《Cpolar》 ?? 感謝大家點(diǎn)贊??收藏?評(píng)論?? 本章主要內(nèi)容為認(rèn)識(shí)與學(xué)習(xí)C++非常重要的概念—— 運(yùn)算符重載 。通過 日期類 的實(shí)現(xiàn),逐步學(xué)習(xí)各個(gè)運(yùn)算符重載的實(shí)現(xiàn)方法即含義。6個(gè)默

    2024年03月18日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包