我想创建一个函数
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<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) = {};
}
我也想知道这种方法的潜在陷阱。
第二个实现具有未定义的行为。该地址没有
helper
,因此您违反了严格别名。
做你想做的事(C++20 后)的定义方法是
template<class T>
void clear(T& v) noexcept
{
std::destroy_at(&v);
std::construct_at(&v);
}
在此之前,您可以向后移植它们。注意: C++17 有
std::destroy_at
,但它对于数组是未定义的。
template<class T>
constexpr void destroy_at(T* p)
{
if constexpr (std::is_array_v<T>)
for (auto &elem : *p)
(destroy_at)(std::addressof(elem));
else
p->~T();
}
template< class T, class... Args >
constexpr T* construct_at(T* p, Args&&... args) {
return ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...);
}