【问题标题】:How to detect if a class has member variables?如何检测一个类是否有成员变量?
【发布时间】:2018-09-06 15:32:49
【问题描述】:

问题

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

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);
    }
};

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

【问题讨论】:

  • 那么如果一个基类有数据成员而不是派生类,没关系?
  • 如果基类也应该为空,您可以使用std::is_empty。否则,目前的C++没有办法。
  • @Rakete1111 任何继承树都不应该有成员变量。
  • @Brian std::is_empty 会检测到我的虚拟会员,所以我不能使用它。动机部分应该已经说明了这一点。
  • @dgnuff std::function 可以轻松打包状态(无数据类也可以,但前提是你有点淘气)。

标签: c++ templates sfinae


【解决方案1】:

您可以通过检查从您的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.(不适用于多重继承)

【讨论】:

  • 这是一个很好的解决方案,但如果类有 多个 vtable 指针,它就会中断。
  • 嗯。这仍然适用于虚拟继承吗? (目前无法测试)。
  • @JesperJuhl 我认为不会,因为虚拟继承会增加大小开销。
  • @JesperJuhl:不幸的是,它没有:(我试图避免它并且没有考虑这个案例。
  • @joe_chip my 评论是关于虚拟继承,而不是多重继承。
【解决方案2】:

您必须以某种方式标记课程。选择一种你喜欢的方式,一个属性或某种类型的带有枚举的整数成员。制作子类的人必须遵循您的约定才能使其发挥作用。

这里的所有其他答案都是这个的一些变体。

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


背景:

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

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

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

C++ 中的模板,除非像 .Net 上的混合模式 C++,也都是在编译时解析的,名称也将全部消失,因此模板本身对您没有任何好处。

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


在纯 C 或 C++ 中,您只需要制作自己的系统,并严格按照字面意思动态跟踪什么是做什么。您可以制作名称字符串的枚举、列表或字典。这是通常所做的,您只需要为自己留下提示。如果不使用 RTTI 的某种形式,则无法以根据定义为未来子类提供隐式可见性的方式编译类。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    相关资源
    最近更新 更多