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

Why does std::optional has pointer-like overhead?

本文关键字:指针 开销 std optional 为什么      更新时间:2023-10-16

问题

我在编译器资源管理器上对std::optional进行了一些测试,令我惊讶的是,它的行为似乎像一个指针,尽管标准(§23.6.3)中规定它应该包含它:

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

测试

我测试的完整代码在这个编译器资源管理器工作表上,尽管我不知道它还有多少时间。这就是为什么我将在这里描述我所做的测试。

我在测试什么

我正在做两个测试,每个测试都有一个功能:

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

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

虽然我不知道这是怎么回事,但它没有任何间接含义。

问题

标题或">我的测试出了什么问题?">

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

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

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

您的自定义类确实使用了8字节的存储空间,将其作为整个qword进行处理,通过算术(移位和掩蔽)提取has_valuevalue,看起来更好,在15秒内无法想到针对std变体的特定缺点。

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


如果std::optional<int>的存储只有4个字节,并且它将自己作为指向动态分配的内存的指针,即空的,它将包含nullptr,并且在将int值存储到其中时,它将在某个地方分配动态内存,并且原始4B存储将保持指向新内存的指针。