【问题标题】:Why does std::optional has pointer-like overhead? [duplicate]为什么 std::optional 有类似指针的开销? [复制]
【发布时间】:2018-05-04 06:18:45
【问题描述】:

问题

我对@9​​87654321@ 进行了一些关于std::optional 的测试,令我惊讶的是,它似乎表现得像一个指针,即使它在标准中有所说明(§23.6.3) 它应该包含它:

不允许实现使用额外的存储空间(例如动态内存)来分配其包含的值。

测试

我测试的完整代码在这个编译器探索器sheet 上,虽然我不知道它还能持续多久。这就是为什么我将在这里描述我所做的测试。

我在测试什么

我正在做两个测试,每个测试一个函数:

  • 检查值是否存在
  • 检查值是否存在,如果存在则检索它,否则为 67780

我使用-O2 minimum 和 --std=c++17 作为 x86_64 目标的 gcc 7.2 和 clang 5.0.0 on 的编译器标志。这里复制的结果来自clang。

使用std::optional

检查

代码:

bool check(const std::optional<int> maybe_int) {
    return maybe_int.has_value();
}

结果:

mov al, byte ptr [rdi + 4]
ret

一个间接。

检索

代码:

int retrieve(const std::optional<int> maybe_int) {
    if(maybe_int.has_value())
        return maybe_int.value();
    else
        return 67780;
}

结果:

cmp byte ptr [rdi + 4], 0
je .LBB1_1
mov eax, dword ptr [rdi]
ret
.LBB1_1:
mov eax, 67780
ret

一个间接用于检查,一个用于检索。

使用自定义类

template<typename T>
class my_optional {
private:
    T val;
    bool has_val;
public:
    /* Constuctors ... */

    bool has_value() const {
        return has_val;
    }
    decltype(auto) value() const {
        return val;
    }
};

检查

代码:

bool check(const my_optional<int> maybe_int) {
    return maybe_int.has_value();
}

结果:

shr rdi, 32
test dil, dil
setne al
ret

无间接。

检索

代码:

int retrieve(const my_optional<int> maybe_int) {
    if(maybe_int.has_value())
        return maybe_int.value();
    else
        return 67780;
}

结果:

movabs rax, 1095216660480
test rdi, rax
mov eax, 67780
cmovne eax, edi
ret

虽然我不知道它是如何工作的,但它没有任何间接性。

问题

标题或“我的测试有什么问题?

【问题讨论】:

    标签: c++ assembly x86-64 c++17 c++-standard-library


    【解决方案1】:

    不允许实现使用额外的存储空间(例如动态内存)来分配其包含的值。

    我将其解读为:他们不允许在std::optional 的基础上使用额外的存储空间。但是 std::optional&lt;int&gt; 看起来被实现为 struct { int value; byte has_value; }; 并作为引用(指针)传递给代码(从机器代码的角度来看,它在 C++ 术语中是“按值传递”,但 CPU 的实际实现是使用任何方式的一级间接),因此对内容的任何访问都需要从该引用/指针间接。

    但是如果你将它从空设置为值,或者删除值,它不会改变分配的存储空间,它会在任何情况下保留这 5 个字节的存储空间(可能填充到 8)。

    您的自定义类确实使用了 8 个字节的存储空间,将其作为整个 qword 使用,通过算术(位移和掩码)提取 has_valuevalue,看起来要好一些,想不到在 15 秒内对 std 变体特别不利。

    它作为值传递,而不是引用。


    如果std::optional&lt;int&gt; 的存储空间只有 4 个字节,并且它本身将作为指向动态分配内存的指针,即为空,它将包含nullptr,并且在存储int 时,该标准措辞将被打破值,它会在某处分配动态内存,而原始的 4B 存储将保留指向新内存的指针。

    【讨论】:

    • 我不是按值传递了 std::optional 吗?
    • @ll-h 啊,很抱歉在答案中产生误导,我是从汇编的角度来描述它,而不是 C++。从 C++ 的角度来看,您按值传递它,重复的答案给出了更好的答案,为什么 C++ 语言确实将 std::optional 视为更复杂的类型,并将其组装为指向实例的指针,而不是简单地传递 64 位值。可能就像当您按值传递 struct 时,它在技术上仍然是指向内存的指针(在堆栈上复制微不足道的结构)。我不是 C++ 专家,我更多的是解释程序集的作用,很抱歉造成混淆。
    • @ll-h:就像重复解释的那样,(以及@Barry 在他删除它之前的回答),即使T 是,libstdc++ 的std::optional&lt;T&gt; 也不是简单可复制的,因此 x86- gcc/clang 使用的 64 个 C++ ABI 通过隐藏指针传递它。这不是类中数据成员的复杂性,而是构造函数/析构函数的编写方式。 (当T 存在时,libc++ 的实现确实使它可以轻松复制。)
    • @ll-h 顺便说一句,我主要试图解释标准措辞的含义(如何不被该间接代码破坏),但如果您从我的回答中一无所获,请告诉我,什么令人困惑/等等。 :)
    猜你喜欢
    • 2014-06-24
    • 2022-09-22
    • 2017-05-28
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-27
    • 2011-03-05
    相关资源
    最近更新 更多