代码之家  ›  专栏  ›  技术社区  ›  Fantastic Mr Fox

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

  •  8
  • Fantastic Mr Fox  · 技术社区  · 7 年前

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

    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

    2 回复  |  直到 7 年前
        1
  •  9
  •   joe_chip    7 年前

    您可以通过依赖于编译器执行空基类优化,通过检查是否有一个类派生自您的 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. (不适用于多重继承)

        2
  •  4
  •   Beeeaaar    7 年前

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


    背景:

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

    在编译之后,您为函数或数据成员使用的任何名称实际上都会消失,因为您知道它并在那里看到它,因此无法按名称查找任何成员。每个数据成员仅通过其相对于类或结构顶部的数值偏移量来确定。

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

    C++中的模板,除非混合模式C++在.NET之类的东西上,也都在编译时解决了,而且名字也会全部消失,所以模板自己买不到。

    像Objective-C这样的语言在某些类型的特殊成员丢失的情况下也不一定会失败,这与您所要求的类似,但是在其使用大量支持代码和运行时管理来独立跟踪的情况下,实际函数本身和它的代码仍然不被注意,并且依赖于其他代码来告诉它们是否存在一个成员,或者是否在空成员上失败。


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

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