我想创建一个函数clear
,这样当像这样调用它时clear(v)
,它会替换v
为相同类型的默认构造值(对于内置类型,这是零初始化)。最简单的解决方案v = {}
不适用于数组,因为您无法为其分配,因此我最终这样做:
template<class T>
void clear(T& v) noexcept
{
if constexpr (std::is_array_v<T>) {
using base_t = std::remove_all_extents_t<T>;
if constexpr (std::is_trivially_default_constructible_v<base_t>)
std::memset(v, 0, sizeof(v));
else {
// To deal with multidimensional arrays, I make sure to get pointers
// the first and last actual value of the array.
base_t* it = reinterpret_cast<base_t*>(v);
base_t* end = it + sizeof(v) / sizeof(base_t);
for (; it != end; ++it)
clear(*it);
}
} else
v = {};
}
我想了解该实现的潜在缺陷,特别是关于别名和对齐规则。我认为实现没问题,但我不确定。检查普通构造函数基类型是因为我必须确保该类型的默认构造函数不会在初始化列表中或通过默认初始化程序为其成员添加自定义初始化值。换句话说,对于该类型,将其零初始化和将其默认值初始化是同义词。
但是实现起来感觉太复杂了,而且我更希望编译器能处理好这一切。我只想修复数组不可分配的问题,所以我最终这样做了:
template<class T>
void clear(T& v) noexcept
{
struct helper { T v; };
*reinterpret_cast<helper*>(&v) = {};
}
我也想知道这种方法的潜在缺陷。
第一个实现对于多维数组来说是极其繁琐的 UB, a
T[M][N]
无法像 a 那样被访问T[M * N]
。第二个实现有未定义的行为。该地址没有
helper
,因此您违反了严格别名。做你想做的事情的一个明确方法是(C++20 之后)
这仍然有一些注意事项。类型必须是 nothrow default 可构造的,nothrow 可破坏的,以免违反
noexcept
,并且对象不得潜在重叠(例如基础子对象)。指向前一个对象的指针和引用现在无效,您只能通过返回的引用访问它。在此之前,您可以反向移植这些。Nb C++17 有
std::destroy_at
,但对于数组而言未定义。destroy_at
或者你也可以直接复制backport的结构destroy_at
这与/版本并不完全相同construct_at
,因为它需要operator=
,而这对于某种类型来说可能不存在,或者它可能执行与默认构造函数不同的操作。