gpt4 book ai didi

c++ - 关于对齐存储和普通可复制/可破坏类型

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:22:49 57 4
gpt4 key购买 nike

我和一个比我聪明的人进行了一次有趣的讨论,我仍然有一个关于对齐存储和普通可复制/可破坏类型的悬而未决的问题。

考虑以下示例:

#include <type_traits>
#include <vector>
#include <cassert>

struct type {
using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
using fn_type = int(storage_type &);

template<typename T>
static int proto(storage_type &storage) {
static_assert(std::is_trivially_copyable_v<T>);
static_assert(std::is_trivially_destructible_v<T>);
return *reinterpret_cast<T *>(&storage);
}

std::aligned_storage_t<sizeof(void *), alignof(void *)> storage;
fn_type *fn;
bool weak;
};

int main() {
static_assert(std::is_trivially_copyable_v<type>);
static_assert(std::is_trivially_destructible_v<type>);

std::vector<type> vec;

type t1;
new (&t1.storage) char{'c'};
t1.fn = &type::proto<char>;
t1.weak = true;
vec.push_back(t1);

type t2;
new (&t2.storage) int{42};
t2.fn = &type::proto<int>;
t2.weak = false;
vec.push_back(t2);

vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto &t) { return t.weak; }), vec.end());

assert(vec.size() == 1);
assert(!vec[0].weak);
assert(vec[0].fn(vec[0].storage) == 42);
}

这是真实案例的简化版本。我真的希望我没有犯错误或过于简化它。

如您所见,想法是存在一个名为 type 的类型(您知道,命名事物很困难)具有三个数据成员:

  • storage 是一堆大小为 sizeof(void *)
  • 的字节
  • fn 指向类型为 int(storage_type &)
  • 的函数的指针
  • weak 一个无用的 bool 值,仅用于介绍示例

为了创建 type 的新实例(参见 main 函数),我输入了一个值(int char) 在存储区和fn 中的静态函数模板proto 的正确特化。
稍后,当我想调用 fn 并获取它返回的整数值时,我会这样做:

int value = type_instance.fn(type_instance.storage);

到目前为止,还不错。尽管存在风险且容易出错(但这是示例,实际用例并非如此),这有效
请注意,type 和我放入存储中的所有类型(示例中的 intchar)都需要是平凡可复制的和平凡的可破坏的。这也是我讨论的核心。

问题(或者更好的是,疑问)出现在我将类型实例放入 vector 中时(参见 main 函数)并决定从阵列中移除其中一个,以便移动其他一些以保持其紧凑。
更一般地说,我不再确定当我想复制或移动 type 的实例时会发生什么,以及它是否是 UB。

我的猜测是允许存储在存储中的类型可以简单地复制和简单地破坏。另一方面,有人告诉我这不是标准直接允许的,它可以被认为是一个良性 UB,因为实际上几乎所有的编译器都允许你这样做(我可以保证这一点,对于 work 的某些定义,它似乎在任何地方都work

因此,问题是:这是允许的还是 UB,在第二种情况下我该怎么做才能解决这个问题?此外,C++20 会为此做出改变吗?

最佳答案

这个问题基本上减少到什么LanguageLawyer建议:

alignas(int) unsigned char buff1[sizeof(int)];
alignas(int) unsigned char buff2[sizeof(int)];

new (buff1) int {42};
std::memcpy(buff2, buff1, sizeof(buff1));

assert(*std::launder(reinterpret_cast<int*>(buff2)) == 42); // is it ok?

换句话说,当我四处复制字节时,我是否也四处复制“对象特性”? buff1确实为 int 提供存储空间- 当我们复制这些字节时,buff2现在还为 int 提供存储空间?

答案是……不。正好有 four ways根据 [intro.object] 创建对象:

An object is created by a definition, by a new-expression ([expr.new]), when implicitly changing the active member of a union, or when a temporary object is created ([conv.rval], [class.temporary]).

这些事情都没有发生在这里,所以我们在 buff2 中没有对象任何类型(在 unsigned char 的普通数组之外),因此行为是未定义的。简单地说,memcpy不创建对象。

在原始示例中,只有第 3 行需要隐式对象创建:

assert(vec.size() == 1); // ok
assert(!vec[0].weak); // ok
assert(vec[0].fn(vec[0].storage) == 42); // UB

这就是为什么 P0593存在并且有一个特殊部分 memmove/memcpy :

A call to memmove behaves as if it

  • copies the source storage to a temporary area
  • implicitly creates objects in the destination storage, and then
  • copies the temporary storage to the destination storage.

This permits memmove to preserve the types of trivially-copyable objects, or to be used to reinterpret a byte representation of one object as that of another object.

这就是您在这里需要的 - 目前 C++ 目前缺少隐式对象创建步骤。


也就是说,您可以或多或少地依赖这种“做正确的事”,因为当今存在的大量 C++ 代码都依赖于这种代码“正常工作”。

关于c++ - 关于对齐存储和普通可复制/可破坏类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54512451/

57 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com