为什么std :: optional有类似指针的开销?

时间:2017-11-20 22:15:44

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

问题

我已经在compiler-explorer上对std::optional进行了一些测试,令我惊讶的是,它看起来像指针一样,即使它已在标准中声明(§23.6。 3 )它应该包含它:

  

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

测试

我测试的完整代码是在这个编译器 - 资源管理器sheet上,虽然我不知道它会保留多长时间。这就是为什么我将在这里描述我做的测试。

我在测试什么

我正在进行两项测试,分别使用一个函数:

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

对于x86_64目标,我使用-O2最小值和--std=c++17作为gcc 7.2和clang 5.0.0的编译器标志。这里复制的结果来自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

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

问题

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

1 个答案:

答案 0 :(得分:-2)

  

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

我读到这一点:他们不允许在std::optional的基础上使用存储顶部的额外存储空间。但std::optional<int>看起来被实现为struct { int value; byte has_value; };并作为参考(指针)传递给代码(从机器代码的角度来看,它是&#34;&#34;传递值&#34 ;在C ++术语中,但CPU的实际实现是以任何方式使用一个间接级别),因此对内容的任何访问都需要来自该引用/指针的间接。

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

您的自定义类确实使用了8个字节的存储空间,将其作为整个qword使用,通过算术(位移和屏蔽)提取has_valuevalue,看起来有点更好的是,在15秒内不能对std变体特别不利。

它作为价值传递,而不是参考。

如果std::optional<int>的存储只有4个字节,那么标准的措辞就会被破坏,它会自动作为动态分配内存的指针,即空它将包含nullptr,并且将int值存储到其中,它会在某处分配动态内存,原始4B存储将指针保留在新内存中。