如果在C++中将成员添加到类中,则会导致编译时错误的技术

Technique to cause compile time error if members are added to class in C++

本文关键字:编译时错误 技术 C++ 成员 添加 如果      更新时间:2023-10-16

假设您有一些POD,其中包含多个公共成员。您还拥有一些类,这些类对来自各种源的这些对象进行序列化和反序列化。除了单元测试之外,是否有任何编码技术可以确保在向类添加成员时,更新任何需要了解添加字段的额外代码。

即,我喜欢一种在所有代码更新以处理新字段之前会导致编译时错误的技术。

C++中没有反射。

但是,一个解决方案是使用static_assert(sizeof(YourType) == x, <message>),其中x是一个硬编码常量,这取决于编译器,它也具有不污染编译代码的吸引力。

添加到YourType的任何成员都将更改sizeof,并导致编译时失败。

(在某些情况下,如果一个新成员占用了以前结构末尾填充的空间,则可能不起作用。请先用编译器尝试这种方法,看看这是否可行。)

c++17开始,您可以利用结构化绑定:您在代码库中添加一个约定,即每当函数要求POD为最新版本时,该函数应通过结构化绑定访问POD:

struct A { int a,b,c; };
void foo( A& the_pod )
{
auto& [a,b,c] = the_pod;
// ... use a,b,c
}

向a添加成员将使代码不再编译。。。

注意,在更改编译器时,依赖A的sizeof可能会破坏代码(假设依赖A的函数(如序列化代码)恰好是可移植的,这是不好的)

此外,错误会告诉你有些是错误的,而这个解决方案也会告诉你哪里是错误的。。。

我看到的唯一缺点是它依赖于程序员约定(但是,假设a是一个只增长的POD也是一个约定…)

在c++11中,如果默认对齐的AggregateType是DefaultConstructable,则可以对其进行完全反射(注意[1]);仅构造反射id非DC。这会很棘手,但草图并没有那么难(署名:magic_get)。

  1. 注意,这些类型有一个拥有类的所有类型的ctor,并且没有其他ctor获取更多的参数。因此,您可以写:

template<typename T, typename... Args>
using constructed_from = decltype(T{std::forward<Args>(std::declval<Args>())...});
  1. 你可以写(谢谢,安东尼)

template<size_t i>
struct Ubiq {
template<typename T>
operator T() const; /* undef'd */
};
  1. 如果您不熟悉,请阅读文档和is_detected的示例:它允许您检测表达式是否有效。在这里,您将从N个Ubiq实例测试T是否是可构造的,即它是否至少有N个参数

在这一点上,你已经准备好了:你只需要从你期望的类型中检查它是否是可构建的,而不是从你的类型和Ubiq中。

如果你想要全反射,你可以添加一个constexpr size_t ctor_param_cnt(),然后实现一个接受template<typename T, size_t i> struct Generator;visit_ctor(),它将通过operator FieldType() const(通常是一个模板)生成每个字段。对于读取访问,您需要存储字段偏移[1]。

[1] 请注意,这需要一个ABI,其中结构的布局仅取决于其中的类型。安腾ABI满足它。


示例(针对您的用例):

#include <string>
#include <type_traits>
struct S1 {
int i;
std::string j;
};
struct S2 {
int i;
std::string j;
int k;
};
template<typename T, typename... Args>
using constructed_from =
typename std::decay<decltype(
T{ std::forward<Args>(std::declval<Args>())... }
)>::type;
template<size_t i>
struct Ubiq {
template<typename T>
operator T() const; /* undef'd */
};
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template<typename T, typename AlwaysVoid, typename... Args>
struct is_constructible_from : std::false_type {};
template<typename T, typename... Args>
struct is_constructible_from<T, void_t<constructed_from<T, Args...>>, Args...> : std::true_type {};
static_assert( is_constructible_from<S1, void, int, std::string>::value, "");
static_assert(!is_constructible_from<S1, void, int, std::string, Ubiq<0>>::value, ""); // note: !
static_assert( is_constructible_from<S2, void, int, std::string>::value, "");
static_assert( is_constructible_from<S2, void, int, std::string, Ubiq<0>>::value, ""); // note: no !