[英]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 <>:
所以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.