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

时间:2018-09-06 15:32:49

标签: c++ templates sfinae

问题

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

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是按滴答滴答进行管理和运行的。它们是经过定制的,可以执行各种任务,例如联系其他设备等。传入的数据可以由例程处理,并确保在每次滴答执行中都可以传入,直到功能完成。根据{{​​1}}类传递正确的数据类型。我不会劝阻未来的人不要将数据错误地添加到功能例程中,因为它不可能实现他们期望的目标。为了解决这个问题,我希望找到一种使用静态断言的方法。

2 个答案:

答案 0 :(得分:9)

您可以依靠编译器进行空的基类优化来解决此问题,方法是检查从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类。

Here's the updated demo.

Here's the original demo.(不适用于多重继承)

答案 1 :(得分:4)

您将不得不以某种方式标记课程。选择一种您喜欢的方式,属性或带有枚举的某种类型的整数成员。凡是创建子类的人都必须遵循您的约定以使其起作用。

这里的所有其他答案将对此有所不同。

任何使用sizeof的答案都不能保证它可以在平台,编译器甚至同一平台和编译器上的类之间运行,因为很容易就能将新成员放入默认类成员对齐方式中,其中大小对于子类,sizeof的大小很容易得出相同的结果。


背景:

如您的代码和问题所述,所有这些都只是普通的和基本的C ad C ++代码,并在编译时完全解决。编译器会告诉您成员是否存在。编译后,它是一堆高效,无名的机器代码,它们本身没有任何提示或帮助。

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

.Net,Java等系统都是为反射而设计的,这是一种通过名称记住类成员的功能,可以在程序运行时在运行时找到它们。

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

类似于Objective-C的语言,即使缺少某些类型的特殊成员,也不一定会失败(类似于您的要求),但是在幕后,它使用了大量支持代码和运行时管理来独立跟踪,实际的函数本身及其代码仍不为人所知,并依靠其他代码来告诉他们某个成员是否存在或对空成员不会失败。


在纯C或C ++中,您将只需要创建自己的系统,并应直言不讳地动态跟踪做什么。您可以创建枚举,名称字符串列表或字典。这通常是完成的,您只需要给自己留下提示即可。如果不能使用某种形式(如果使用RTTI的话),则不能按照定义通过隐式可见的方式对将来的子类进行编译。

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

相关问题