避免返回by-reference参数

时间:2016-05-03 00:21:03

标签: c++ c++11

假设我有一个班级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 { ... }

但我认为这可能是模棱两可的,因为左值和右值都适合第二个版本。

4 个答案:

答案 0 :(得分:4)

  

但是,我认为以下代码不安全:

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

你是对的。这就是std::optional::value_or()返回T而不是T&T const&的原因。根据{{​​3}}中的基本原理:

  

有人认为函数应该通过常量引用而不是值返回,这样可以避免在某些情况下的复制开销:

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()更简洁。

答案 1 :(得分:0)

由于您的类的用户可以期望从Option::get()返回的引用仅与Option对象的生命周期的特定实例一样有效,因此您可以合理地对此进行相同的期望。从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,并且可以轻松避免存储重复项。

答案 2 :(得分:0)

我可以建议重新设计这门课吗?

它有一个默认的ctor,它可以让val_为nullptr,但是它同时有一个get()因为取消引用(*)而可能抛出异常。它还设计为在shared_prt中保存T,但将其作为参考返回。

让客户知道它为空:

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&lt;&gt;明确:

  1. 返回有效指针,或
  2. 抛出bad_alloc异常; 它不会返回null
  3. 所以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_;
    };
    

答案 3 :(得分:0)

我宁愿通过引用返回,让调用者决定是否要存储引用或返回值的副本。