- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我和一个比我聪明的人进行了一次有趣的讨论,我仍然有一个关于对齐存储和普通可复制/可破坏类型的悬而未决的问题。
考虑以下示例:
#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
和我放入存储中的所有类型(示例中的 int
和 char
)都需要是平凡可复制的和平凡的可破坏的。这也是我讨论的核心。
问题(或者更好的是,疑问)出现在我将类型实例放入 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/
C++11(和 C++14)STL 容器有 noexcept析构函数和 clear()成员函数。 这意味着元素应该有 noexcept析构函数,或者至少存储在容器中的实际元素在被销毁时不应该抛出任何异
我是一名优秀的程序员,十分优秀!