亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

一、背景

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

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

二、跟蹤值

假設有一個類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)建它的同一條語句中被銷毀。當調用print時,字符串已經(jīng)被破壞,使用它是非法的,并導致未定義的行為。

為了說明這一點,如果將std::string替換為類型X,并且在X的析構函數(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_;
};

在調用的地方也打印日志:

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)在調用它:

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

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

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

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

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

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

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

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

三、存儲variant

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

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

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

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

將得到一個編譯錯誤:

variant must have no reference alternative

為了達到我們的目的,需要將引用放在另一個類型中;即必須編寫特定的代碼來處理數(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)建使用相同的技術,除了variantConstReference之外,它不能產(chǎn)生非const引用。然而,當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
    );
}

進一步優(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)調用時帶左值:

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

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

(2)使用臨時值:

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

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

六、總結

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

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

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

相關文章

  • C語言中的盜賊(小偷)問題詳解

    C語言中的盜賊(小偷)問題詳解

    大家好,本篇文章主要講的是C語言中的盜賊(小偷)問題詳解,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • 淺談c和c++的某些小區(qū)別

    淺談c和c++的某些小區(qū)別

    下面小編就為大家?guī)硪黄獪\談c和c++的某些小區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • C語言中的字符(char)詳細講解

    C語言中的字符(char)詳細講解

    本篇文章主要介紹C語言中char的知識,并附有代碼實例,以便大家在學習的時候更好的理解,有需要的可以看一下
    2016-07-07
  • C++結構體初始化的10種寫法總結

    C++結構體初始化的10種寫法總結

    這篇文章主要為大家詳細介紹了10種C++中結構體初始化的寫法,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-04-04
  • linux下基于C語言的信號編程實例

    linux下基于C語言的信號編程實例

    這篇文章主要介紹了linux下基于C語言的信號編程,實例分析了信號量的基本使用技巧與相關概念,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • 詳解state狀態(tài)模式及在C++設計模式編程中的使用實例

    詳解state狀態(tài)模式及在C++設計模式編程中的使用實例

    這篇文章主要介紹了state狀態(tài)模式及在C++設計模式編程中的使用實例,在設計模式中策略用來處理算法變化,而狀態(tài)則是透明地處理狀態(tài)變化,需要的朋友可以參考下
    2016-03-03
  • C語言實現(xiàn)2048游戲

    C語言實現(xiàn)2048游戲

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)2048小游戲,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • 使用OpenCV實現(xiàn)檢測和追蹤車輛

    使用OpenCV實現(xiàn)檢測和追蹤車輛

    這篇文章主要為大家詳細介紹了使用OpenCV實現(xiàn)檢測和追蹤車輛,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C++利用map實現(xiàn)并查集

    C++利用map實現(xiàn)并查集

    這篇文章主要為大家詳細介紹了C++利用map實現(xiàn)并查集,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • C++繼承中的對象構造與析構和賦值重載詳解

    C++繼承中的對象構造與析構和賦值重載詳解

    這篇文章主要為大家詳細介紹了C++繼承中的對象構造與析構和賦值重載,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03

最新評論