欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你掌握C++中的移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā)

 更新時(shí)間:2023年12月14日 10:24:21   作者:ACE叫牌  
這篇文章主要為大家詳細(xì)介紹了C++中的移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們深入掌握C++有一定的幫助,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

移動(dòng)語(yǔ)義

C++11新特性的std::move()用于將一個(gè)左值轉(zhuǎn)換為右值引用。它并不是實(shí)際移動(dòng)或復(fù)制數(shù)據(jù),而是通過(guò)將一個(gè)左值強(qiáng)制轉(zhuǎn)換為一個(gè)右值引用來(lái)實(shí)現(xiàn)對(duì)對(duì)象的轉(zhuǎn)移。這個(gè)特性在C++11中引入,用于優(yōu)化對(duì)象移動(dòng)操作的效率。

我們知道,右值引用只能引用右值,如果嘗試綁定左值就會(huì)編譯錯(cuò)誤。

int i = 0;
int &&k = i;    // 編譯錯(cuò)誤

在C++11標(biāo)準(zhǔn)中可以在不創(chuàng)建臨時(shí)值的情況下顯式地將左值通過(guò)static_cast轉(zhuǎn)換為將亡值,通過(guò)值類(lèi)別的內(nèi)容我們知道將亡值屬于右值,所以可以被右值引用綁定。值得注意的是,由于轉(zhuǎn)換的并不是右值,因此它依然有著和轉(zhuǎn)換之前相同的生命周期和內(nèi)存地址,例如:

int i = 0;
int &&k = static_cast<int&&>(i);

既然這個(gè)轉(zhuǎn)換既不改變生命周期,也不改變內(nèi)存地址,那它存在的意義是什么?實(shí)際上它最大的作用是讓左值使用移動(dòng)語(yǔ)義。

舉例:

#include <iostream>

class BigMemoryPool
{
public:
    static const int PoolSize = 4096;

    BigMemoryPool() : pool_(new char[PoolSize])
    {
        std::cout << "普通構(gòu)造函數(shù)" << std::endl;
    }

    ~BigMemoryPool()
    {
        if (pool_ != nullptr)
        {
            delete[] pool_;
        }
    }

    BigMemoryPool(BigMemoryPool &&other) : pool_(new char[PoolSize])
    {
        std::cout << "移動(dòng)構(gòu)造函數(shù)" << std::endl;
        pool_ = other.pool_;
        other.pool_ = nullptr;
    }

    BigMemoryPool(const BigMemoryPool &other) : pool_(new char[PoolSize])
    {
        std::cout << "拷貝構(gòu)造函數(shù)" << std::endl;
        memcpy(pool_, other.pool_, PoolSize);
    }

private:
    char *pool_;
};

BigMemoryPool get_pool(const BigMemoryPool &pool)
{
    return pool;
}

BigMemoryPool make_pool()
{
    BigMemoryPool pool;
    return get_pool(pool);
}

int main()
{
    BigMemoryPool my_pool1;
    BigMemoryPool my_pool2 = my_pool1;
    BigMemoryPool my_pool3 = static_cast<BigMemoryPool &&>(my_pool1);

    return 0;
}

在這段代碼中,my_pool1是一個(gè)BigMemoryPool類(lèi)型的對(duì)象,也是一個(gè)左值,所以用它去構(gòu)造my_pool2的時(shí)候調(diào)用的是復(fù)制構(gòu)造函數(shù)。為了讓編譯器調(diào)用移動(dòng)構(gòu)造函數(shù)構(gòu)造my_pool3,這里使用了static_cast<BigMemoryPool &&>(my_pool1)將my_pool1強(qiáng)制轉(zhuǎn)換為右值(也是將亡值,為了敘述思路的連貫性后面不再?gòu)?qiáng)調(diào))。由于調(diào)用了移動(dòng)構(gòu)造函數(shù),my_pool1失去了自己的內(nèi)存數(shù)據(jù),后面的代碼也不能對(duì)my_pool1進(jìn)行操作了。

結(jié)果輸出:

PS C:\Users\zh'n\Desktop\新建文件夾> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夾> ./main
普通構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)
移動(dòng)構(gòu)造函數(shù)

但是這個(gè)示例中把my_pool1這個(gè)左值轉(zhuǎn)換成my_pool3這個(gè)左值似乎沒(méi)有什么意義,而且程序員如果再次去訪問(wèn)my_pool1還會(huì)引發(fā)未定義行為。

正確的使用場(chǎng)景是在一個(gè)右值被轉(zhuǎn)換為左值后需要再次轉(zhuǎn)換為右值,最典型的例子是一個(gè)右值作為實(shí)參傳遞到函數(shù)中。我們?cè)谟懻撟笾岛陀抑档臅r(shí)候曾經(jīng)提到過(guò),無(wú)論一個(gè)函數(shù)的實(shí)參是左值還是右值,其形參都是一個(gè)左值,即使這個(gè)形參看上去是一個(gè)右值引用,例如:

void move_pool(BigMemoryPool &&pool)
{
  std::cout << "call move_pool" << std::endl;
  BigMemoryPool my_pool(pool);
}

int main()
{
  move_pool(make_pool());
}

結(jié)果輸出:

PS C:\Users\zh'n\Desktop\新建文件夾> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夾> ./main
普通構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)
移動(dòng)構(gòu)造函數(shù)
call move_pool
拷貝構(gòu)造函數(shù)

代碼中,make_pool()返回的是一個(gè)臨時(shí)對(duì)象,也是一個(gè)右值,move_pool的參數(shù)是一個(gè)右值引用,但是在使用形參pool去構(gòu)造my_pool時(shí)調(diào)用的是拷貝構(gòu)造函數(shù)。如果我們想調(diào)用移動(dòng)構(gòu)造函數(shù)的話,需要把形參pool強(qiáng)制轉(zhuǎn)換為右值。

void move_pool(BigMemoryPool &&pool)
{
    std::cout << "call move_pool" << std::endl;
    BigMemoryPool my_pool = static_cast<BigMemoryPool &&>(pool); // 1
}

結(jié)果輸出:

PS C:\Users\zh'n\Desktop\新建文件夾> g++ -std=c++11 -fno-elide-constructors main.cpp -o main
PS C:\Users\zh'n\Desktop\新建文件夾> ./main
普通構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)
移動(dòng)構(gòu)造函數(shù)
call move_pool
移動(dòng)構(gòu)造函數(shù)

請(qǐng)注意,在這個(gè)場(chǎng)景下強(qiáng)制轉(zhuǎn)換為右值就沒(méi)有任何問(wèn)題了,因?yàn)閙ove_pool函數(shù)的實(shí)參是make_pool返回的臨時(shí)對(duì)象,當(dāng)函數(shù)調(diào)用結(jié)束后臨時(shí)對(duì)象就會(huì)被銷(xiāo)毀,所以轉(zhuǎn)移其內(nèi)存數(shù)據(jù)不會(huì)存在任何問(wèn)題。

在C++11的標(biāo)準(zhǔn)庫(kù)中還提供了一個(gè)函數(shù)模板std::move幫助我們將左值轉(zhuǎn)換為右值,這個(gè)函數(shù)內(nèi)部也是用static_cast做類(lèi)型轉(zhuǎn)換。只不過(guò)由于它是使用模板實(shí)現(xiàn)的函數(shù),因此會(huì)根據(jù)傳參類(lèi)型自動(dòng)推導(dǎo)返回類(lèi)型,省去了指定轉(zhuǎn)換類(lèi)型的代碼。另一方面從移動(dòng)語(yǔ)義上來(lái)說(shuō),使用std::move函數(shù)的描述更加準(zhǔn)確。所以建議讀者使用std::move將左值轉(zhuǎn)換為右值而非自己使用static_cast轉(zhuǎn)換,例如:

void move_pool(BigMemoryPool &&pool)
{
    std::cout << "call move_pool" << std::endl;
    BigMemoryPool my_pool(std::move(pool)); // 1
}

總結(jié):

std::move()內(nèi)部是用static_cast做類(lèi)型轉(zhuǎn)換,只不過(guò)它是使用模板實(shí)現(xiàn)的函數(shù),因此會(huì)根據(jù)傳參類(lèi)型自動(dòng)推導(dǎo)返回值類(lèi)型,省去了指定類(lèi)型的代碼。如果使用std::move()將一個(gè)左值轉(zhuǎn)換為右值并賦值給其他對(duì)象后,這個(gè)對(duì)象就會(huì)被銷(xiāo)毀,所以在函數(shù)調(diào)用過(guò)程中,創(chuàng)建N個(gè)對(duì)象實(shí)際上只是把第一個(gè)對(duì)象的內(nèi)存不斷的轉(zhuǎn)移,類(lèi)似層層遞歸。 這樣做的好處就是省去了創(chuàng)建對(duì)象的開(kāi)銷(xiāo),并且在對(duì)象副本龐大的情況下節(jié)省了大量時(shí)間。

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

在了解完美轉(zhuǎn)發(fā)之前,先了解一下什么是萬(wàn)能引用和引用折疊。

我們知道常量左值引用可以引用左值,也可以引用右值,是一個(gè)幾乎的萬(wàn)能引用,但是因?yàn)樗某A啃詫?dǎo)致使用受限制。

在C++11中有一個(gè)“萬(wàn)能引用”,例如:

void foo(int &&i){} // 右值引用

template<class T>
void bar(T &&t){} // 萬(wàn)能引用

int get_val(){return 5;}
int &&x = get_val(); // 右值引用
auto &&x = get_val(); // 萬(wàn)能引用

我們可以發(fā)現(xiàn),只要是自動(dòng)類(lèi)型推導(dǎo)的引用就是萬(wàn)能引用。在這個(gè)推導(dǎo)過(guò)程中,源對(duì)象是左值,那就推導(dǎo)為左值引用,源對(duì)象是右值,那就推導(dǎo)為右值引用。

萬(wàn)能引用能如此靈活地引用對(duì)象,實(shí)際上是因?yàn)樵贑++11中添加了一套引用疊加推導(dǎo)的規(guī)則——引用折疊。在這套規(guī)則中規(guī)定了在不同的引用類(lèi)型互相作用的情況下應(yīng)該如何推導(dǎo)出最終類(lèi)型。

舉例說(shuō)明:

int i = 42;
const int j = 11;
bar(i);
bar(j);
bar(get_val());

auto &&x = i;
auto &&y = j;
auto &&z = get_val();

在bar(i);中i是一個(gè)左值,所以T的推導(dǎo)類(lèi)型結(jié)果是int&,根據(jù)引用折疊規(guī)則int& &&的最終推導(dǎo)類(lèi)型為int&,于是bar函數(shù)的形參是一個(gè)左值引用。而在bar(get_val());中g(shù)et_val返回的是一個(gè)右值,所以T的推導(dǎo)類(lèi)型為非引用類(lèi)型int,于是最終的推導(dǎo)類(lèi)型是int&&,bar函數(shù)的形參成為一個(gè)右值引用。

完美轉(zhuǎn)發(fā)的用途

看一個(gè)常規(guī)的轉(zhuǎn)發(fā)函數(shù)模板

#include <iostream>
#include <string>
#include <typeinfo>

template<class T>
void show_type(T t)
{
  std::cout << typeid(t).name() << std::endl;
}

template<class T>
void normal_forwarding(T t)
{
  show_type(t);
}

int main()
{
  std::string s = "hello world";
  normal_forwarding(s);
}

// 輸出:Ss

normal_forwarding函數(shù)可以完成字符串的轉(zhuǎn)發(fā)任務(wù),但是它的效率很慢。首先它的參數(shù)是值傳遞,那么在轉(zhuǎn)發(fā)過(guò)程中就會(huì)發(fā)生一次臨時(shí)對(duì)象的復(fù)制。其中一個(gè)解決方法就是把void normal_forwarding(T t)換成void normal_forwarding(T& t),通過(guò)引用傳遞,但這是一個(gè)左值引用,如果參數(shù)是一個(gè)右值就會(huì)編譯失敗。

std::string get_string()
{
  return "hi world";
}

normal_forwarding(get_string());    // 編譯失敗

但是常量左值可以引用右值,可以解決這個(gè)問(wèn)題,但引來(lái)的新問(wèn)題是常量左值引用具有常量性,使得對(duì)象不可以被修改。

所以萬(wàn)能引用的誕生解決了這個(gè)問(wèn)題。

對(duì)于萬(wàn)能引用來(lái)說(shuō),如果實(shí)參是一個(gè)左值,那么形參會(huì)被推導(dǎo)為左值引用、如果實(shí)參是一個(gè)右值,那么形參會(huì)被推導(dǎo)為右值引用。

#include <iostream>
#include <string>

template<class T>
void show_type(T t)
{
  std::cout << typeid(t).name() << std::endl;
}

template<class T>
void perfect_forwarding(T &&t)	// 萬(wàn)能引用
{
  show_type(static_cast<T&&>(t));
}

std::string get_string()
{
  return "hi world";
}

int main()
{
  std::string s = "hello world";
  perfect_forwarding(s);
  perfect_forwarding(get_string());
}

和移動(dòng)語(yǔ)義的情況一樣,顯式使用static_cast類(lèi)型轉(zhuǎn)換進(jìn)行轉(zhuǎn)發(fā)不是一個(gè)便捷的方法。在C++11的標(biāo)準(zhǔn)庫(kù)中提供了一個(gè)std::forward函數(shù)模板,在函數(shù)內(nèi)部也是使用static_cast進(jìn)行類(lèi)型轉(zhuǎn)換,只不過(guò)使用std::forward轉(zhuǎn)發(fā)語(yǔ)義會(huì)表達(dá)得更加清晰,std::forward函數(shù)模板的使用方法也很簡(jiǎn)單:

template<class T>
void perfect_forwarding(T &&t)
{
  show_type(std::forward<T>(t));
}

請(qǐng)注意std::move和std::forward的區(qū)別,其中std::move一定會(huì)將實(shí)參轉(zhuǎn)換為一個(gè)右值引用,并且使用std::move不需要指定模板實(shí)參,模板實(shí)參是由函數(shù)調(diào)用推導(dǎo)出來(lái)的。而std::forward會(huì)根據(jù)左值和右值的實(shí)際情況進(jìn)行轉(zhuǎn)發(fā),在使用的時(shí)候需要指定模板實(shí)參。

完整示例:

#include <iostream>
#include <string>
#include <typeinfo>

template <class T>
void show_type(T t)
{
    std::cout << typeid(t).name() << std::endl;
}

template <class T>
void perfect_forwarding(T &&t)
{
    show_type(std::forward<T>(t));
}

int main()
{
    std::string s = "hello world";
    perfect_forwarding(s);	// 實(shí)參是左值
    perfect_forwarding(1.0); // 實(shí)參是右值
}

// 輸出
// Ss
// d

總結(jié)

完美轉(zhuǎn)發(fā)允許將函數(shù)的參數(shù)(包括左值和右值)轉(zhuǎn)發(fā)給其他函數(shù),同時(shí)保持原始參數(shù)的值不變,這樣可以實(shí)現(xiàn)高效的函數(shù)調(diào)用。

#include <iostream>
#include <utility>

template <typename T>
void process(T &i)
{
    std::cout << "L-value: " << i << std::endl;
}

template <typename T>
void process(T &&i)
{
    std::cout << "R-value: " << i << std::endl;
}

template <typename T>
void forwarder(T &&t)
{
    process(std::forward<T>(t));
}

int main()
{
    int a = 42;
    forwarder(a); // L-value: 42

    forwarder(7.1); // R-value: 7

    return 0;
}

// 輸出
// L-value: 42
// R-value: 7.1

在上面的示例中,forwarder函數(shù)使用了完美轉(zhuǎn)發(fā),它接受一個(gè)泛型類(lèi)型的參數(shù)T&& t,并將參數(shù)t轉(zhuǎn)發(fā)給process函數(shù)。通過(guò)使用std::forward(t),可以將原始參數(shù)的值類(lèi)別(左值或右值)傳遞給process函數(shù),從而調(diào)用合適的重載函數(shù)。

通過(guò)使用完美轉(zhuǎn)發(fā),可以更好地處理函數(shù)參數(shù)的轉(zhuǎn)發(fā),避免不必要的拷貝,提高代碼的性能和效率。請(qǐng)注意,完美轉(zhuǎn)發(fā)需要注意避免懸垂引用和引用折疊等問(wèn)題,在實(shí)際使用中需要謹(jǐn)慎處理。

以上就是一文帶你掌握C++中的移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā)的詳細(xì)內(nèi)容,更多關(guān)于C++移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)順序表的示例代碼

    C語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)順序表的示例代碼

    順序表是用一段物理地址連續(xù)的存儲(chǔ)單元依次存儲(chǔ)數(shù)據(jù)元素的線性結(jié)構(gòu)。順序表一般分為靜態(tài)順序表和動(dòng)態(tài)順序表,本文主要和大家介紹的是動(dòng)態(tài)順序表的實(shí)現(xiàn),需要的可以參考一下
    2022-10-10
  • C++使用easyx畫(huà)實(shí)時(shí)走動(dòng)的鐘表

    C++使用easyx畫(huà)實(shí)時(shí)走動(dòng)的鐘表

    這篇文章主要為大家詳細(xì)介紹了C++使用easyx畫(huà)實(shí)時(shí)走動(dòng)的鐘表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C語(yǔ)言多組輸入使用方法

    C語(yǔ)言多組輸入使用方法

    這篇文章主要給大家介紹了關(guān)于C語(yǔ)言多組輸入使用的相關(guān)資料,在 C語(yǔ)言中可以使用循環(huán)語(yǔ)句來(lái)實(shí)現(xiàn)多組輸入,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • C++?拷貝構(gòu)造函數(shù)與賦值的區(qū)別

    C++?拷貝構(gòu)造函數(shù)與賦值的區(qū)別

    拷貝構(gòu)造函數(shù)和賦值函數(shù)非常容易混淆,本文主要介紹了C++?拷貝構(gòu)造函數(shù)與賦值的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-04-04
  • C語(yǔ)言中進(jìn)行函數(shù)指針回調(diào)的實(shí)現(xiàn)步驟

    C語(yǔ)言中進(jìn)行函數(shù)指針回調(diào)的實(shí)現(xiàn)步驟

    在 C 語(yǔ)言中,函數(shù)指針的回調(diào)是一種強(qiáng)大的編程技術(shù),它允許我們?cè)谔囟ǖ氖录l(fā)生或特定的條件滿足時(shí),調(diào)用由用戶定義的函數(shù),這種機(jī)制增加了程序的靈活性和可擴(kuò)展性,使得代碼更具通用性和可重用性,本文給大家介紹了C語(yǔ)言中進(jìn)行函數(shù)指針回調(diào)的實(shí)現(xiàn)步驟,需要的朋友可以參考下
    2024-07-07
  • 基于C語(yǔ)言實(shí)現(xiàn)圖書(shū)管理信息系統(tǒng)設(shè)計(jì)

    基于C語(yǔ)言實(shí)現(xiàn)圖書(shū)管理信息系統(tǒng)設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了基于C語(yǔ)言實(shí)現(xiàn)圖書(shū)管理信息系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C++函數(shù)模板的使用詳解

    C++函數(shù)模板的使用詳解

    大家好,本篇文章主要講的是C++函數(shù)模板的使用詳解,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • 基于matlab MFCC+GMM的安全事件聲學(xué)檢測(cè)系統(tǒng)

    基于matlab MFCC+GMM的安全事件聲學(xué)檢測(cè)系統(tǒng)

    這篇文章主要為大家介紹了基于matlab MFCC+GMM的安全事件聲學(xué)檢測(cè)系統(tǒng)實(shí)現(xiàn)及源碼示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-02-02
  • OpenGL實(shí)現(xiàn)貝塞爾曲線或曲面

    OpenGL實(shí)現(xiàn)貝塞爾曲線或曲面

    這篇文章主要為大家詳細(xì)介紹了OpenGL實(shí)現(xiàn)貝塞爾曲線或曲面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C語(yǔ)言實(shí)現(xiàn)數(shù)學(xué)表達(dá)式運(yùn)算

    C語(yǔ)言實(shí)現(xiàn)數(shù)學(xué)表達(dá)式運(yùn)算

    這篇文章主要為大家詳細(xì)介紹了c語(yǔ)言實(shí)現(xiàn)數(shù)學(xué)表達(dá)式運(yùn)算,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11

最新評(píng)論