[C++] std::optional与RVO:最高效的std::optional实践与探究

这篇具有很好参考价值的文章主要介绍了[C++] std::optional与RVO:最高效的std::optional实践与探究。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

返回值优化RVO

在cppreference中,是这么介绍RVO的

In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization."

即在返回函数内部临时变量(非函数参数,非catch参数)时,如果该参数的的类型和函数返回值类型相同,编译器就被允许去直接构造返回值(即使copy/move构造函数具有副作用)。

std::optional

std::optional是在C++17引入的,常用于有可能构造失败的函数,作为函数的返回值。

在cppreference中,std::optional的例子如下:

点击查看代码
#include <iostream>
#include <optional>
#include <string>
 
// optional can be used as the return type of a factory that may fail
std::optional<std::string> create(bool b)
{
    if (b)
        return "Godzilla";
    return {};
}
 
// std::nullopt can be used to create any (empty) std::optional
auto create2(bool b)
{
    return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}
 
int main()
{
    std::cout << "create(false) returned "
              << create(false).value_or("empty") << '\n';
 
    // optional-returning factory functions are usable as conditions of while and if
    if (auto str = create2(true))
        std::cout << "create2(true) returned " << *str << '\n';
}

一个尴尬的情况是这个例子并没有介绍在函数内部构造一个左值变量然后返回的情况,于是乎网上就出现了很多种return optional的写法。本文就想探讨下究竟哪一种写法才是最高效的。

实验

参数

编译器:x86-64 gcc 13.2

编译参数 -O1 -std=c++17

基于compiler explorer

准备工作

假设我们原始的函数具有以下形式

A always_success_0(int n) {
    A temp(someFn(n));
    return temp;
}

如果单纯作为可能fail的函数的一层包装,一种很自然的想法是只把函数的返回值改为std::optional,而函数体不变,即

optional<A> introduce_option_0(int n) {
    A temp(someFn(n));
    return temp;
}

很明显这会破坏NRVO的条件,但究竟相差多少呢?有没有挽回办法?

我找了网上目前常见的写法,我们可能有以下变体

optional<A> introduce_option_0(int n) {
    A temp(someFn(n));
    return temp;
}

optional<A> introduce_option_1(int n) {
    A temp(someFn(n));
    return std::move(temp);
}

optional<A> introduce_option_2(int n) {
    A temp(someFn(n));
    return {temp};
}

optional<A> introduce_option_3(int n) {
    A temp(someFn(n));
    return {std::move(temp)};
}

为了探究NRVO的条件和优化程度,对原本的函数也使用这4种变体

A always_success_0(int n) {
    A temp(someFn(n));
    return temp;
}

A always_success_1(int n) {
    A temp(someFn(n));
    return std::move(temp);
}

A always_success_2(int n) {
    A temp(someFn(n));
    return {temp};
}

A always_success_3(int n) {
    A temp(someFn(n));
    return {std::move(temp)};
}

同时让我们定义struct A

struct A{
    int ctx;
    A(int x) noexcept {
        ctx=x+1;
        printf("default construct");
        }
    A(const A&) noexcept {
        printf("copy construct");
    }
    A(A&& ano) noexcept {
        printf("move construct");
    }
    ~A() noexcept {
        printf("destruct");
    }
};

tips:

使用noexcept使编译器允许进一步优化,否则汇编会增加一段异常处理,如下图所示

[C++] std::optional与RVO:最高效的std::optional实践与探究[C++] std::optional与RVO:最高效的std::optional实践与探究

同时为了方便定位,防止编译器进一步优化,我们将someFn写成一个具有副作用的函数

int someFn(int n) {
    int x;
    scanf("%d",&x);
    return x+n;
}

现在我们有了进行编译的所有代码:

点击查看代码
#include <cstdio>
#include <optional>
using std::optional;

int someFn(int n) {
    int x;
    scanf("%d",&x);
    return x+n;
}

struct A{
    int ctx;
    A(int x) noexcept {
        ctx=x+1;
        printf("default construct");
        }
    A(const A&) noexcept {
        printf("copy construct");
    }
    A(A&& ano) noexcept {
        printf("move construct");
    }
    ~A() noexcept {
        printf("destruct");
    }
    A& operator=(const A&) {
        printf("copy op");
    }
    A& operator=(A&&) {
        printf("move op");
    }
};

A always_success_0(int n) {
    A temp(someFn(n));
    return temp;
}

A always_success_1(int n) {
    A temp(someFn(n));
    return std::move(temp);
}

A always_success_2(int n) {
    A temp(someFn(n));
    return {temp};
}

A always_success_3(int n) {
    A temp(someFn(n));
    return {std::move(temp)};
}

optional<A> introduce_option_0(int n) {
    A temp(someFn(n));
    return temp;
}

optional<A> introduce_option_1(int n) {
    A temp(someFn(n));
    return std::move(temp);
}

optional<A> introduce_option_2(int n) {
    A temp(someFn(n));
    return {temp};
}

optional<A> introduce_option_3(int n) {
    A temp(someFn(n));
    return {std::move(temp)};
}

编译

[C++] std::optional与RVO:最高效的std::optional实践与探究

我们可以看到always_success_0函数触发了RVO,只调用了一次构造函数。而always_success_1没有进行RVO,额外调用了移动构造函数和析构函数,这也是滥用std::move的一个后果。

[C++] std::optional与RVO:最高效的std::optional实践与探究

再看到introduce_option_0函数,它与发生移动的always_success的汇编代码相比,只多了一行设置std::optional::_Has_value布尔值的汇编。

函数 默认构造 拷贝构造 移动构造 析构 设置bool
always_success_0 1
always_success_1 1 1 1
always_success_2 1 1 1
always_success_3 1 1 1
introduce_option_0 1 1 1 1
introduce_option_1 1 1 1 1
introduce_option_2 1 1 1 1
introduce_option_3 1 1 1 1
*modify_reference *2 *1 1

*为UE库中一些形如以下的函数,

bool modify_reference(int n, A& out) {
    out = someFn(n);
    return true;
}

*算上了函数调用前的接收者的默认构造

*函数内会调用移动赋值=而不是移动构造

Best result

可以观察到,触发了RVO的汇编会精简很多,我们要想方设法去触发RVO。以下两种改良都可以触发RVO

A not_always_success_best(int n, bool &b) {
    A temp(someFn(n));
    b = true;
    return temp;
}

optional<A> optional_best(int n) {
    optional<A> temp(someFn(n));
    return temp;
}
[C++] std::optional与RVO:最高效的std::optional实践与探究

可以看到这两种方式的函数体的汇编是一样的,不一样的只有参数传递时对栈的操作。

总结

std::optional最高效的写法是触发RVO的写法,即:文章来源地址https://www.toymoban.com/news/detail-694074.html

optional<A> optional_best(int n) {
    optional<A> temp(someFn(n));
    return temp;
}

到了这里,关于[C++] std::optional与RVO:最高效的std::optional实践与探究的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【SpringCloud】深入探究Eureka:构建微服务架构中的高效服务发现系统

    👨‍💻博主主页:小尘要自信 在现代的软件开发中,微服务架构已经成为了一个热门的话题。微服务架构的一个关键组成部分就是服务发现。而在服务发现领域,Eureka无疑是一个备受推崇的解决方案。本篇博客将为您介绍什么是Eureka以及如何在您的微服务架构中应用它。

    2024年02月14日
    浏览(33)
  • 深入探究Selenium定位技巧及最佳实践

    在使用Selenium进行Web自动化测试时,准确地定位元素是非常重要的一步。Selenium提供了多种元素定位方法,本文将深入探究这八大元素定位方法,帮助读者更好地理解和应用Selenium的定位技巧。 1. ID定位 ID是元素在HTML中的唯一标识符,因此使用ID进行定位是最直接、最快速的方

    2024年01月21日
    浏览(32)
  • “深入探究SpringMVC的工作原理与入门实践“

    SpringMVC是一个流行的Java Web开发框架,它提供了一种优雅的方式来构建灵活、可扩展的Web应用程序。本文将介绍SpringMVC的基本概念,深入探讨其工作流程和核心组件,并提供一个入门程序来帮助读者快速上手。 SpringMVC是基于Java的MVC(Model-View-Controller)设计模式的Web框架。它通

    2024年02月10日
    浏览(30)
  • 探究企业角色权限管理的重要性及实践方法

    角色权限管理是企业网盘工具中的重要功能。它是指将特定角色分配给用户,然后根据用户的工作要求为这些角色分配访问权限的过程。通过使用基于角色的权限,组织可以确保员工只能访问执行工作职责所需的文件和文件夹。 那么企业角色权限管理有必要吗? 角色权限管

    2024年02月06日
    浏览(24)
  • 深入探究序列化与反序列化:原理、应用和最佳实践

    序列化 (Serialization)是指将对象转化为字节流的过程,以便于存储或通过网络进行传输。 反序列化 (Deserialization)则是将字节流转化为对象的过程,恢复原始对象的状态。 在计算机科学中,序列化和反序列化是常用的数据处理技术,用于在不同系统、不同编程语言之间传

    2024年02月08日
    浏览(35)
  • C++ | 探究拷贝对象时的一些编译器优化

    👑作者主页:@烽起黎明 🏠学习社区:烈火神盾 🔗专栏链接:C++ 在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的 经过深度探索类的六大天选之子学习,我们讲到了拷贝构造一些基本概念和调用形式 经过构造函数

    2023年04月19日
    浏览(49)
  • 炫技亮点 使用Optional类优化代码,提升可读性和简化空值处理

    在日常的软件开发中,我们经常需要处理可能为空的值,例如 从数据库查询数据 、 调用外部接口获取数据 、 从配置文件读取配置项 等。传统的处理方式往往需要使用 繁琐的空值判断和异常处理 代码,使得代码变得冗长和难以理解。为了解决这个问题,Java 8 引入了 Optio

    2024年02月13日
    浏览(40)
  • RC4Drop加密技术:原理、实践与安全性探究

    1.1 加密技术的重要性 加密技术在当今信息社会中扮演着至关重要的角色。通过加密,我们可以保护敏感信息的机密性,防止信息被未经授权的用户访问、窃取或篡改。加密技术还可以确保数据在传输过程中的安全性,有效防止信息泄露和数据被篡改的风险。在网络通信、电

    2024年04月22日
    浏览(34)
  • ChatGPT探索系列之三:探究ChatGPT的训练、优化和应用方法

    ChatGPT发展到目前,其实网上已经有大量资料了,博主做个收口,会出一个ChatGPT探索系列的文章,帮助大家深入了解ChatGPT的。整个系列文章会按照一下目标来完成: 理解ChatGPT的背景和应用领域; 学习GPT模型系列的发展历程和原理; 探究ChatGPT的训练、优化和应用方法; 分析

    2023年04月09日
    浏览(25)
  • 探究Vue3中的Composition API:优化组件逻辑的新利器

    在 Vue 3.0 中,引入了一种新的响应式 API,即 toRef 。 toRef 函数可以将一个普通值转换为响应式引用类型,这样就可以在模板中直接使用这个响应式引用类型的属性,并且当该属性发生变化时,视图会自动更新。 通过控制台打印输出的内容和页面的变化,我们可以观察到,age的

    2024年02月10日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包