最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++在同一對象中存儲左值或右值的方法

 更新時間:2025年03月24日 10:52:33   作者:Lion 萊恩呀  
C++ 代碼似乎經(jīng)常出現(xiàn)一個問題:如果該值可以來自左值或右值,則對象如何跟蹤該值?即如果保留該值作為引用,那么就無法綁定到臨時對象,本文給大家介紹了C++在同一對象中存儲左值或右值的幾種方法,需要的朋友可以參考下

一、背景

C++ 代碼似乎經(jīng)常出現(xiàn)一個問題:如果該值可以來自左值或右值,則對象如何跟蹤該值?即如果保留該值作為引用,那么就無法綁定到臨時對象。如果將其保留為一個值,那么當(dāng)它從左值初始化時,會產(chǎn)生不必要的副本。

有幾種方法可以應(yīng)對這種情況。使用std::variant提供了一個很好的折衷方案來獲得有表現(xiàn)力的代碼。

二、跟蹤值

假設(shè)有一個類MyClass。想讓MyClass訪問某個std::string。如何表示MyClass內(nèi)部的字符串?
有兩種選擇:

  • 將其存儲為引用。
  • 將其存儲為副本。

2.1、存儲引用

如果將其存儲為引用,例如const引用:

class MyClass
{
public:
    explicit MyClass(std::string const& s) : s_(s) {}
    void print() const
    {
        std::cout << s_ << '\n';
    }
private:
    std::string const& s_;
};

則可以用一個左值初始化我們的引用:

std::string s = "hello";
MyClass myObject{s};
myObject.print();

看起來很不錯。但是,如果想用右值初始化我們的對象呢?例如:

MyClass myObject{std::string{"hello"}};
myObject.print();

或者這樣的代碼:

std::string getString(); // function declaration returning by value

MyClass myObject{getString()};
myObject.print();

那么代碼具有未定義的行為。原因是,臨時字符串對象在創(chuàng)建它的同一條語句中被銷毀。當(dāng)調(diào)用print時,字符串已經(jīng)被破壞,使用它是非法的,并導(dǎo)致未定義的行為。

為了說明這一點,如果將std::string替換為類型X,并且在X的析構(gòu)函數(shù)打印日志:

struct X
{
    ~X() { std::cout << "X destroyed" << '\n';}
};

class MyClass
{
public:
    explicit MyClass(X const& x) : x_(x) {}
    void print() const
    {
        // using x_;
    }
private:
    X const& x_;
};

在調(diào)用的地方也打印日志:

MyClass myObject(X{});
std::cout << "before print" << '\n';
myObject.print();

輸出:

X destroyed
before print

可以看到,在嘗試使用之前,這個X已經(jīng)被破壞了。

完整示例:

#include <iostream>
#include <string>

struct X
{
    ~X() { std::cout << "X destroyed" << '\n';}
};

class MyClass
{
public:
    explicit MyClass(X const& x) : x_(x) {}
    void print()
    {
        (void) x_; // using x_;
    }
private:
    X const& x_;
};

int main()
{
	MyClass myObject(X{});
	std::cout << "before print" << '\n';
	myObject.print();
}

2.2、存儲值

另一種選擇是存儲一個值。這允許使用move語義將傳入的臨時值移動到存儲值中:

class MyClass
{
public:
    explicit MyClass(std::string s) : s_(std::move(s)) {}
    void print() const
    {
        std::cout << s_ << '\n';
    }
private:
    std::string s_;
};

現(xiàn)在調(diào)用它:

MyClass myObject{std::string{"hello"}};
myObject.print();

產(chǎn)生兩次移動(一次構(gòu)造s,一次構(gòu)造s_),并且沒有未定義的行為。實際上,即使臨時對象被銷毀,print也會使用類內(nèi)部的實例。

不幸的是,如果帶著左值返回到第一個調(diào)用點:

std::string s = "hello";
MyClass myObject{s};
myObject.print();

那么就不再做兩次移動了:做了一次復(fù)制(構(gòu)造s)和一次移動(構(gòu)造s_)。

更重要的是,我們的目的是給MyClass訪問字符串的權(quán)限,如果做一個拷貝,就有了一個不同于進(jìn)來的實例。所以它們不會同步。

對于臨時對象來說,這不是問題,因為它無論如何都會被銷毀,并且我們在之前將它移了進(jìn)來,所以仍然可以訪問字符串。但是通過復(fù)制,我們不再給MyClass訪問傳入字符串的權(quán)限。

所以存儲一個值也不是一個好的解決方案。

三、存儲variant

存儲引用不是一個好的解決方案,存儲值也不是一個好的解決方案。我們想做的是,如果引用是從左值初始化的,則存儲引用;如果引用是從右值初始化的,則存儲引用。

但是數(shù)據(jù)成員只能是一種類型:值或引用,對嗎?

但是,對于std::variant,它可以是任意一個。不過,如果嘗試在一個變量中存儲引用,就像這樣:

std::variant<std::string, std::string const&>

將得到一個編譯錯誤:

variant must have no reference alternative

為了達(dá)到我們的目的,需要將引用放在另一個類型中;即必須編寫特定的代碼來處理數(shù)據(jù)成員。如果為std::string編寫這樣的代碼,則不能將其用于其他類型。

在這一點上,最好以通用的方式編寫代碼。

四、通用存儲類

存儲需要是一個值或一個引用。既然現(xiàn)在是為通用目的編寫這段代碼,那么也可以允許非const引用。由于變量不能直接保存引用,那么可以將它們存儲到包裝器中:

template<typename T>
struct NonConstReference
{
    T& value_;
    explicit NonConstReference(T& value) : value_(value){};
};

template<typename T>
struct ConstReference
{
    T const& value_;
    explicit ConstReference(T const& value) : value_(value){};
};

template<typename T>
struct Value
{
    T value_;
    explicit Value(T&& value) : value_(std::move(value)) {}
};

將存儲定義為這兩種情況之一:

template<typename T>
using Storage = std::variant<Value<T>, ConstReference<T>, NonConstReference<T>>;

現(xiàn)在需要通過提供引用來訪問變量的底層值。創(chuàng)建了兩種類型的訪問:一種是const,另一種是非const

4.1、定義const訪問

要定義const訪問,需要使變量內(nèi)部的三種可能類型中的每一種都產(chǎn)生一個const引用。

為了訪問變量中的數(shù)據(jù),將使用std::visit和規(guī)范的overload 模式,這可以在c++ 17中實現(xiàn):

template<typename... Functions>
struct overload : Functions...
{
    using Functions::operator()...;
    overload(Functions... functions) : Functions(functions)... {}
};

要獲得const引用,只需為每種variant創(chuàng)建一個:

template<typename T>
T const& getConstReference(Storage<T> const& storage)
{
    return std::visit(
        overload(
            [](Value<T> const& value) -> T const&             { return value.value_; },
            [](NonConstReference<T> const& value) -> T const& { return value.value_; },
            [](ConstReference<T> const& value) -> T const&    { return value.value_; }
        ),
        storage
    );
}

4.2、定義非const訪問

非const引用的創(chuàng)建使用相同的技術(shù),除了variantConstReference之外,它不能產(chǎn)生非const引用。然而,當(dāng)std::visit訪問一個變量時,必須為它的每一個可能的類型編寫代碼:

template<typename T>
T& getReference(Storage<T>& storage)
{
    return std::visit(
        overload(
            [](Value<T>& value) -> T&             { return value.value_; },
            [](NonConstReference<T>& value) -> T& { return value.value_; },
            [](ConstReference<T>& ) -> T&.        { /* code handling the error! */ }
        ),
        storage
    );
}

進(jìn)一步優(yōu)化,拋出一個異常:

struct NonConstReferenceFromReference : public std::runtime_error
{
    explicit NonConstReferenceFromReference(std::string const& what) : std::runtime_error{what} {}
};

template<typename T>
T& getReference(Storage<T>& storage)
{
    return std::visit(
        overload(
            [](Value<T>& value) -> T&             { return value.value_; },
            [](NonConstReference<T>& value) -> T& { return value.value_; },
            [](ConstReference<T>& ) -> T& { throw NonConstReferenceFromReference{"Cannot get a non const reference from a const reference"} ; }
        ),
        storage
    );
}

五、創(chuàng)建存儲

已經(jīng)定義了存儲類,可以在示例中使用它來訪問傳入的std::string,而不管它的值類別:

class MyClass
{
public:
    explicit MyClass(std::string& value) :       storage_(NonConstReference(value)){}
    explicit MyClass(std::string const& value) : storage_(ConstReference(value)){}
    explicit MyClass(std::string&& value) :      storage_(Value(std::move(value))){}

    void print() const
    {
        std::cout << getConstReference(storage_) << '\n';
    }

private:
    Storage<std::string> storage_;
};

(1)調(diào)用時帶左值:

std::string s = "hello";
MyClass myObject{s};
myObject.print();

匹配第一個構(gòu)造函數(shù),并在存儲成員內(nèi)部創(chuàng)建一個NonConstReference。當(dāng)print函數(shù)調(diào)用getConstReference時,非const引用被轉(zhuǎn)換為const引用。

(2)使用臨時值:

MyClass myObject{std::string{"hello"}};
myObject.print();

這個函數(shù)匹配第三個構(gòu)造函數(shù),并將值移動到存儲中。getConstReference然后將該值的const引用返回給print函數(shù)。

六、總結(jié)

variant為c++中跟蹤左值或右值的經(jīng)典問題提供了一種非常適合的解決方案。這種技術(shù)的代碼具有表現(xiàn)力,因為std::variant允許表達(dá)與我們的意圖非常接近的東西:“根據(jù)上下文,對象可以是引用或值”。

在C++ 17和std::variant之前,解決這個問題很棘手,導(dǎo)致代碼難以正確編寫。隨著語言的發(fā)展,標(biāo)準(zhǔn)庫變得越來越強(qiáng)大,可以用越來越多的表達(dá)性代碼來表達(dá)我們的意圖。

以上就是C++在同一對象中存儲左值或右值的方法的詳細(xì)內(nèi)容,更多關(guān)于C++同一對象存儲左值的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言實現(xiàn)個稅計算器

    C語言實現(xiàn)個稅計算器

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)個稅計算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • VSCode中C/C++編碼亂碼問題的兩種解決方法

    VSCode中C/C++編碼亂碼問題的兩種解決方法

    在中國地區(qū),Windows 系統(tǒng)中的 cmd 和 PowerShell 默認(rèn)編碼是 GBK,但 VSCode 默認(rèn)使用 UTF-8 編碼,這種編碼不一致會導(dǎo)致在 VSCode 終端中運行 C/C++ 程序時出現(xiàn)亂碼,以下介紹兩種方法來解決這一問題,需要的朋友可以參考下
    2025-03-03
  • VS2019/2022配置C++ OpenCV4.10.0環(huán)境的圖文步驟

    VS2019/2022配置C++ OpenCV4.10.0環(huán)境的圖文步驟

    本文主要介紹了VS2019/2022配置C++ OpenCV4.10.0環(huán)境的圖文步驟,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-03-03
  • C++變量初始化形式及其默認(rèn)初始值問題

    C++變量初始化形式及其默認(rèn)初始值問題

    這篇文章主要介紹了C++變量初始化形式及其默認(rèn)初始值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 使用C++描繪心形

    使用C++描繪心形

    本文給大家分享的是一個使用c++繪制心形的代碼,雖然情人節(jié)已經(jīng)過去了,但是只要有心,天天都是情人節(jié)~~哈哈,讓那些說程序猿都是木頭的人去死吧。
    2015-03-03
  • sizeof()的簡單介紹

    sizeof()的簡單介紹

    sizeof操作符以字節(jié)形式給出了其操作數(shù)的存儲大小
    2013-04-04
  • 關(guān)于vector的常見用法詳解

    關(guān)于vector的常見用法詳解

    這篇文章主要介紹了關(guān)于vector的常見用法詳解,vector本身可以作為數(shù)組使用,而且在一些元素個數(shù)不確定的場合可以很好地節(jié)省空間,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • C語言簡單實現(xiàn)計算字符個數(shù)的方法

    C語言簡單實現(xiàn)計算字符個數(shù)的方法

    這篇文章主要介紹了C語言簡單實現(xiàn)計算字符個數(shù)的方法,涉及C語言針對字符串的簡單遍歷與判定技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • C++之STL--string的使用

    C++之STL--string的使用

    這篇文章主要介紹了C++之STL--string的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • C++虛函數(shù)表實例分析

    C++虛函數(shù)表實例分析

    這篇文章主要介紹了C++虛函數(shù)表,對多態(tài)中用到的虛函數(shù)表進(jìn)行了比較通俗的實例分析,需要的朋友可以參考下
    2014-09-09

最新評論

墨脱县| 南岸区| 营口市| 普陀区| 忻州市| 唐河县| 福贡县| 铁岭市| 荔波县| 英吉沙县| 赤水市| 东光县| 万年县| 临安市| 永靖县| 祁阳县| 德钦县| 千阳县| 岳阳市| 屏东市| 峨眉山市| 宜宾市| 海林市| 松滋市| 宁海县| 乌拉特前旗| 石柱| 建湖县| 乐业县| 青河县| 桃江县| 柳林县| 乌审旗| 随州市| 沈丘县| 大理市| 建始县| 嘉义市| 伊通| 龙川县| 新晃|