首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >避免引用返回参数

避免引用返回参数
EN

Stack Overflow用户
提问于 2016-05-03 00:21:02
回答 4查看 536关注 0票数 8

假设我有一个类Option

代码语言:javascript
运行
复制
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为空时返回的默认值:

代码语言:javascript
运行
复制
Option<int> x;  // empty
int y = 123;
x.getOrElse(y);  // == 123

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

代码语言:javascript
运行
复制
Option<int> x;
x.getOrElse(123);  // reference to temporary variable!

一种更安全的方法是从Option::getOrElse返回值,但如果Option不是空的,那将是浪费的。我能设法解决这个问题吗?

更新:我正在考虑如何重载getOrElse的参数类型(lvalue/rvalue),但还没有确切地知道如何做到这一点。

更新2:,也许这个?

代码语言:javascript
运行
复制
T getOrElse(T &&x) const { ... }

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

但我认为这可能是模棱两可的,因为lvalue和rvalue参数都符合第二个版本。

EN

回答 4

Stack Overflow用户

发布于 2016-05-03 00:50:52

但是,我认为以下代码不安全: 选项x;x.getOrElse(123);//引用临时变量!

你是对的。这就是为什么std::optional::value_or()返回T而不是T&T const&的原因。根据N3672中的基本原理

有人认为,函数应该通过不断引用而不是值来返回,这将避免在某些情况下复制开销: 无效观察(const& x);可选ox { /* . */ };观察( ox.value_or(X{args}) );//不必要的副本 但是,只有当可选对象作为临时对象(没有名称)提供时,函数value_or的好处才是可见的;否则,三元操作符同样有用: 可选ox { /* .* };观察(ox?*ok : X{args});//无副本 此外,如果可选对象被断开,通过引用返回可能会呈现一个悬空引用,因为第二个参数通常是临时的: 可选的ox {nullopt};auto&x= ox.value_or(X{args});cout << x;// x正在悬挂!

我建议你遵循同样的准则。如果您确实需要避免复制,请使用三元。这是安全和无版权的:

代码语言:javascript
运行
复制
Optional<int> ox = ...;
const int& val = ox ? *ox : 123;

如果您真的没有,或者Optional是一个rvalue,那么getOrElse()就更简洁了。

票数 4
EN

Stack Overflow用户

发布于 2016-05-03 01:14:39

由于类的用户只能期望Option::get()返回的引用与Option对象生命周期的特定实例一样有效,因此您可以合理地对从Option::getOrElse()返回的内容做出同样的期望。

在这种情况下,对象维护它需要为客户端保持活动的事物集合可能是一种可接受的开销:

代码语言:javascript
运行
复制
#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,并且很容易地避免存储重复项。

票数 0
EN

Stack Overflow用户

发布于 2016-05-04 07:12:47

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

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

让客户端知道它是空的:

代码语言:javascript
运行
复制
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_;
};

客户端代码更改如下:

代码语言:javascript
运行
复制
Option option;
const T & ref = option.getOrElse(123);

将是:

代码语言:javascript
运行
复制
Option option;
const T & ref = option.IsNull() ? 123 : option.get();

为什么我删除: if (val_ == nullptr) {

让我们澄清一下make_shared<>:

  1. 返回有效指针,或
  2. 抛出bad_alloc异常;它不返回空

所以IsNull()也是无用的,它应该是这样的:

代码语言:javascript
运行
复制
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?选项对象可以多次移动或复制吗?或者我更喜欢这样的设计:

代码语言:javascript
运行
复制
template<typename T>
class Option {
public:
    Option(T val) noexcept : val_(std::move(val))
    {}

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

private:
    T val_;
};
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36993646

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档