如何检测类是否有成员变量?

How to detect if a class has member variables?

本文关键字:是否 成员 变量 何检测 检测      更新时间:2023-10-16

>问题

我想检测一个类是否有成员变量,如果有,则静态断言失败。 像这样:

struct b {
int a;
}
static_assert(!has_member_variables<b>, "Class should not contain members"). // Error.
struct c {
virtual void a() {}
void other() {}
}
static_assert(!has_member_variables<c>, "Class should not contain members"). // Fine.
struct d : c {
}
static_assert(!has_member_variables<d>, "Class should not contain members"). // Fine.
struct e : b {
}
static_assert(!has_member_variables<e>, "Class should not contain members"). // Error.
struct f : c {
char z;
}
static_assert(!has_member_variables<f>, "Class should not contain members"). // Error.

有没有办法使用 SFINAE 模板实现这一目标?此类可能具有继承,甚至与虚函数具有多重继承(尽管基类中没有成员(。

赋予动机

我有一个非常简单的设置,如下所示:

class iFuncRtn {
virtual Status runFunc(Data &data) = 0;
};
template <TRoutine, TSpecialDataType>
class FuncRoutineDataHelper : public iFuncRtn {
Status runFunc(Data &data) {
static_assert(!has_member_variables<TRoutine>, "Routines shouldnt have data members!");
// Prepare special data for routine
TSpecialDataType sData(data);
runFuncImpl(sData);
}
class SpecificRtn : 
public FuncRoutineDataHelper<SpecificRtn, MySpecialData> {
virtual Status runFuncImpl(MySpecialData &sData) {
// Calculate based on input 
sData.setValue(someCalculation);
}
};

FunctionalityRoutine是按每个即时报价管理和运行的。它们是定制的,可以执行各种各样的任务,例如联系其他设备等。传入的数据可以由例程操作,并保证在每次逐笔报价执行时传入,直到功能完成。根据DataHelper类传入正确类型的数据。我不想阻止未来的人们错误地将数据添加到功能例程中,因为不太可能达到他们的期望。为了强制这样做,我希望找到一种静态断言的方法。

您可以通过依赖执行空基类优化的编译器来解决此问题,方法是检查从您的T派生的类是否与具有虚函数的空类具有相同的大小:

template<typename T, typename... BaseClasses>
class IsEmpty
{
// sanity check; see the updated demo below
static_assert(IsDerivedFrom<T, BaseClasses...>::value);
struct NonDerived : BaseClasses... { virtual ~NonDerived() = default; };
struct Derived : T { virtual ~Derived() = default; };
public:
inline static constexpr bool value = (sizeof(NonDerived) == sizeof(Derived));
};

这应该适用于单继承和多继承。但是,使用多重继承时,有必要列出所有基类,如下所示:

static_assert(IsEmpty<Derived, Base1, Base2, Base3>::value);

显然,此解决方案排除了final类。

这是更新的演示。

这是原始演示。(不适用于多重继承(

您必须以某种方式标记类。选择一种您喜欢的方式,一个属性或某种带有枚举的整数类型成员。无论谁制作子类,都必须遵循您的约定才能使其正常工作。

这里的所有其他答案都将是这个的某种变体。

任何使用 sizeof 的答案都不能保证这将在平台、编译器甚至同一平台和编译器上的类之间工作,因为可以轻松地在默认的类成员对齐中容纳一个新成员,其中 sizeof 的大小很容易最终与子类相同。


背景:

如您的代码和问题中所述,所有这些都只是简单而基本的 C 和 C++ 代码,并且在编译时完全解决。编译器将告诉您成员是否存在。 编译后,它是高效、无名的机器代码的混合体,本身没有任何提示或帮助。

编译后,您用于函数或数据成员的任何名称都会有效地消失,正如您所知并在那里看到的那样,并且无法按名称查找任何成员。每个数据成员仅通过其与类或结构顶部的数字偏移量来知道。

像.Net,Java等系统是为反射而设计的,反射是按名称记住类成员的能力,您可以在运行时在程序运行时找到它们。

C++中的模板,除非在 .Net 之类的东西上C++混合模式,否则也会在编译时解析,并且名称也将全部消失,因此模板本身不会给您带来任何好处。

像 Objective-C 这样的语言也被编写为如果缺少某些类型的特殊成员,则不一定会失败,类似于您所问的,但在幕后,它使用大量支持代码和运行时管理来独立跟踪,其中实际函数本身及其代码仍然不知道并依靠其他代码来告诉它们成员是否存在或不在空成员上失败。


在纯 C 或 C++ 中,您只需创建自己的系统,并动态跟踪什么做什么。您可以创建枚举,或名称字符串的列表或字典。这是通常的做法,您只需要为自己留下提示即可。一个类不能以根据定义为未来的子类提供隐式可见性的方式进行编译,如果不使用 RTTI 的某种形式。

出于这个原因,将类型成员放在类上是很常见的,这可能是一个简单的枚举。我不会指望大小或任何可能依赖于平台的东西。