簡體   English   中英

避免返回按引用參數

[英]Avoid returning by-reference argument

假設我有一個類Option

template<typename T>
class Option {
public:
    Option() noexcept
    {}

    Option(T val) noexcept : val_(std::make_shared<T>(std::move(val)))
    {}

    const T & get() const
    {
        if (val_ == nullptr) {
            throw std::out_of_range("get on empty Option");
        }
        return *val_;
    }

    const T & getOrElse(const T &x) const
    {
        return val_ == nullptr ? x : *val_;
    }

private:
    std::shared_ptr<T> val_;
};

傳遞給Option::getOrElse的參數是當此Option為空時返回的默認值:

Option<int> x;  // empty
int y = 123;
x.getOrElse(y);  // == 123

但是,我認為以下代碼不安全:

Option<int> x;
x.getOrElse(123);  // reference to temporary variable!

一種更安全的方法是從Option::getOrElse返回值,但是當Option為非空時這將很浪費。 我可以以某種方式解決此問題嗎?

更新:我正在考慮也許重載getOrElse的參數類型(左值/右值),但是還沒有確切地知道如何做到這一點。

更新2:也許是嗎?

T getOrElse(T &&x) const { ... }

const T & getOrElse(const T &x) const { ... }

但是我認為這可能是模棱兩可的,因為左值和右值參數都適合第二個版本。

但是,我認為以下代碼不安全:

 Option<int> x; x.getOrElse(123); // reference to temporary variable! 

你是對的。 這就是為什么std::optional::value_or()返回T而不是T&T const& 根據N3672中的基本原理

有人爭辯說,該函數應按常量引用而不是值返回,這樣可以避免在某些情況下的復制開銷:

 void observe(const X& x); optional<X> ox { /* ... */ }; observe( ox.value_or(X{args}) ); // unnecessary copy 

但是,僅當可選對象作為臨時對象(沒有名稱)提供時,函數value_or的好處才可見。 否則,三元運算符同樣有用:

 optional<X> ox { /* ... */ }; observe(ox ? *ok : X{args}); // no copy 

另外,如果取消了可選對象的作用,則按引用返回可能會產生懸空引用,因為第二個參數通常是臨時的:

 optional<X> ox {nullopt}; auto&& x = ox.value_or(X{args}); cout << x; // x is dangling! 

我建議您遵循相同的准則。 如果確實需要避免復制,請使用三元。 這是安全且不可復制的:

Optional<int> ox = ...;
const int& val = ox ? *ox : 123;

如果您確實不這樣做,或者Optional仍然是右值,則getOrElse()更簡潔。

由於類的用戶可以期望從Option::get()返回的引用僅與Option對象生命周期的特定實例一樣有效,因此您可以合理地期望從Option::getOrElse()返回的內容相同Option::getOrElse()

在那種情況下,對象維護它需要為客戶端保持生命的一系列事物可能是可以接受的開銷:

#include <list>
#include <memory>
#include <iostream>

template<typename T>
class Option {
public:
    Option() noexcept
    {}

    Option(T val) noexcept : val_(std::make_shared<T>(std::move(val)))
    {}

    const T & get() const
    {
        if (val_ == nullptr) {
            throw std::out_of_range("get on empty Option");
        }
        return *val_;
    }

    const T & getOrElse(const T &x) const
    {
        if (val_ == nullptr) {
            std::cout << "storing const T &\n";
            elses_.push_front(x);
            return elses_.front();
        }

        return *val_;
    }

    const T & getOrElse(T &&x) const
    {
        if (val_ == nullptr) {
            std::cout << "storing T && by move\n";
            elses_.push_front(std::move(x));
            return elses_.front();
        }

        return *val_;
    }


private:
    std::shared_ptr<T> val_;
    mutable std::list<T> elses_;
};


int main()
{
    Option<int> x;  // empty
    int y = 123;
    auto rx = x.getOrElse(y);  // == 123

    auto & rxx = x.getOrElse(42); 

    std::cout << "rx = " << rx << "\n";
    std::cout << "rxx = " << rxx << "\n";
}

Option::getOrElse()返回的引用將一直有效,只要Option::get()返回的引用將一直有效。 當然,這也意味着Option::getOrElse()可以引發異常。

作為一個小的改進,如果T類型可以用作關聯容器的鍵,則可以使用其中一個代替std::list並且可以輕松地避免存儲重復項。

我可以建議重新設計該課程嗎?

它具有默認的ctor,可以將val_保留為nullptr,但同時具有get(),由於取消引用(*)可能會引發異常。 它還設計為將T保​​存在shared_prt中,但將其作為參考返回。

讓客戶端知道它為空:

template<typename T>
class Option {
public:
    Option() noexcept
    {}

    Option(T val) noexcept : val_(std::make_shared<T>(std::move(val)))
    {}

    const T & get() const
    {
        return *val_;
    }

    bool IsNull() const
    {
        return val_ == nullptr;
    }

private:
    std::shared_ptr<T> val_;
};

客戶端代碼從:

Option option;
const T & ref = option.getOrElse(123);

成為:

Option option;
const T & ref = option.IsNull() ? 123 : option.get();

為什么刪除:if(val_ == nullptr){

讓我們弄清楚make_shared <>:

  1. 返回一個有效的指針,或者
  2. 拋出bad_alloc異常; 它不返回null

所以IsNull()也是無用的,應該像這樣:

template<typename T>
class Option {
public:
    Option(T val) noexcept : val_(std::make_shared<T>(std::move(val)))
    {}

    const T & get() const
    {
        return *val_;
    }

private:
    std::shared_ptr<T> val_;
};

為什么要使用shared_ptr? 選項對象可以移動或復制幾次? 否則我更喜歡將其設計為:

template<typename T>
class Option {
public:
    Option(T val) noexcept : val_(std::move(val))
    {}

    const T & get() const
    {
        return val_;
    }

private:
    T val_;
};

我寧願按引用返回,並讓調用者決定是否要存儲對返回值的引用或副本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM