关于对齐的存储和少量可复制/可破坏的类型 [英] About aligned storage and trivially copyable/destructible types

查看:64
本文介绍了关于对齐的存储和少量可复制/可破坏的类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我和一个比我聪明的人进行了有趣的讨论,但我仍然对开放式存储和可复制/可破坏的类型问题持开放态度.

I had an interesting discussion with a guy smarter than me and I remained with an open question about aligned storage and trivially copyable/destructible types.

请考虑以下示例:

#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);
}

这是真实案例的简化版本.我真的希望我不要犯错误或简化得太多.

This is a simplified version of a real world case. I really hope I didn't make errors or simplified it too much.

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

As you can see, the idea is that there exists a type called type (naming things is hard, you know) having three data members:

  • storage是一束大小为sizeof(void *)
  • 的字节
  • fn指向类型为int(storage_type &)
  • 的函数的指针
  • weak一个无用的布尔值,仅用于介绍示例
  • storage that is a bunch of byte having size sizeof(void *)
  • fn a pointer to a function having type int(storage_type &)
  • weak an useless bool used only to introduce the example

要创建type的新实例(请参见main函数),我在存储区域中放置了一个值(intchar)和静态函数模板的正确专业化<fn中的c10>.
稍后,当我想调用 fn并获取返回的整数值时,我会执行以下操作:

To create new instances of type (see the main function), I put a value (either an int or a char) in the storage area and the right specialization of the static function template proto in fn.
Later on, when I want to invoke fn and get the integer value it returns, I do something like this:

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

到目前为止,太好了.尽管存在风险和容易出错的事实(但这只是一个例子,实际的用例却没有),但这可行.
请注意,type和我放入存储中的所有类型(在示例中为intchar)都必须是可复制的和可破坏的.这也是我讨论的核心.

So far, so good. Despite the fact of being risky and error-prone (but this is an example, the real use case is not), this works.
Note that type and all the types I put in the storage (int and char in the example) are required to be both trivially copyable and trivially destructible. This is also the core of the discussion I had.

当我将类型的实例(例如,放在向量中)并决定删除一个类型的实例时,就会出现问题(或者更好的是, doubt )其中的一个从数组中移出,以便其他一些移到另一个位置以保持打包.
一般来说,我不再确定要复制或移动type实例以及是否为UB时会发生什么.

The problem (or better, the doubt) arises when I put instances of types eg in a vector (see the main function) and decide to remove one of them from within the array, so that some of the others are moved around to keep it packed.
More in general, I'm no longer that sure about what happens when I want to copy or move instances of type and if it's UB or not.

我的猜测是,允许将存储类型中的类型轻松地复制和轻松地破坏.另一方面,有人告诉我标准不直接允许这样做,可以将其视为良性UB ,因为实际上几乎所有的编译器都允许您这样做(可以保证这一点,对于 work 的某些定义,似乎到处都是 work .

My guess was that it was allowed being the types put in the storage trivially copyable and trivially destructible. On the other side, I've been told that this isn't directly allowed by the standard and it can be considered a benign UB, because almost all the compilers in fact allows you to do that (I can guarantee this, it seemes to work everywhere for some definitions of work).

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

So, the question is: is this allowed or UB and what can I do to work around the issue in the second case? Moreover, is C++20 going to change things for that?

推荐答案

此问题基本上可以归结为

This problem reduces to basically what LanguageLawyer suggested:

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提供存储了?

In other words - when I copy bytes around, do I also copy around "object-ness"? buff1 is certainly providing storage for an int - when we copy those bytes, does buff2 also now provide storage for an int?

答案是……不.每个[ intro.object]:

And the answer is... no. There are exactly four ways to create an object, per [intro.object]:

在隐式更改联合的活动成员时或在创建临时对象([conv.rval],[class]时),该对象由定义,新表达式([expr.new])创建.temporary].

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不会创建对象.

None of those things happened here, so we don't have an object in buff2 of any kind (outside of just the normal array of unsigned char), hence behavior is undefined. Simply put, memcpy does not create objects.

在原始示例中,仅第三行需要创建隐式对象:

In the original example, it's only the 3rd line that requires that implicit object creation:

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


这就是为什么 P0593 的原因存在,并且有memmove/memcpy的特殊部分:


This is why P0593 exists and has a special section for memmove/memcpy:

调用memmove的行为就像

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 ++目前缺少隐式对象创建步骤.

This is what you need here - that implicit object creation step is currently missing from C++ today.

也就是说,您可以或多或少地依赖此做正确的事",因为当今存在的大量C ++代码主体都依赖此代码来正常工作".

That said, you can more or less rely on this "doing the right thing" given the simply enormous body of C++ code that exists today relies on this code to "just work."

这篇关于关于对齐的存储和少量可复制/可破坏的类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆