类型特征的优势相对于静态成员是什么?

4

我有一个类(Voxel),其中包含可能具有不同属性(材质,密度等)的子类,具有获取和设置方法。现在,我想编写如下代码:

template <typename VoxelType>
void process(VoxelType voxel)
{
  if(VOXEL_HAS_MATERIAL)
  {
    //Do some work which involves calling get/setMaterial()
  }
  if(VOXEL_HAS_DENSITY)
  {
    //Do some work which involves calling get/setDensity()
  }
}

因此,我希望实现VOXEL_HAS_MATERIALVOXEL_HAS_DENSITY的部分。 有两个简单的选择:
  1. Voxel类添加静态的hasMaterial()hasDensity()方法,以在派生类中重写。
  2. 创建一个类型特征类,其中包含hasMaterial()hasDensity(),并为每个Voxel子类进行专门化。
使用方法(2)允许定义原始类型(int等)的特征,但在我的情况下这没有用处。在这里使用类型特征有什么进一步的优势还是采用更简单的静态方法呢?
注意:我也知道基于SFINAE的方法,我会单独考虑它们。
编辑1:我已更改示例代码以显示模板的使用。 我正在寻找静态而不是运行时解决此问题的解决方案。 理想情况下,编译器将能够剥离if语句中的代码,如果它确定它们不能针对给定类型执行,则可以剥离该代码。

1
"void process(Voxel voxel)" - 你的意思是 "SomeVoxelSubclass voxel" 吗? - Abyx
2
“静态方法可以在派生类中被重写”是什么意思? - Luchian Grigore
1
顺便提一下,还有第三种选择:定义一个自由函数 bool has_material(Voxel&),让子类的作者重载它或者使用 ADL 进行重载。如果它被内联并返回一个常量,特别是在 C++11 中将其设为 constexpr,那么优化器应该能够像使用类型特征一样高效地处理它。 - Steve Jessop
@PolyVox:这只是一个术语问题,如果它不是虚函数,那么根据定义你没有覆盖它(10.3/2定义了“覆盖”)。你已经“隐藏”了基类函数。 - Steve Jessop
@Steve - 感谢你的回复。是的,我的术语不太准确,我确实是指它被隐藏了而不是被覆盖了。我从来没有传递基类的指针(一切都通过模板完成),所以对我来说隐藏是可以接受的。 - David Williams
显示剩余2条评论
4个回答

4

类型特征(type-traits)非常有用,因为即使无法更改类型本身,也可以轻松地将其添加到类型中。此外,使用类型特征,您可以简单地提供一个合理的默认值(例如,您可以将hasMaterialhasDensity委托给类的适当静态成员),然后只需针对不适用于此默认值的类专门化该特征。


这个帖子里有很多好的信息,但是这个答案最直接地回答了我的问题。已采纳。 - David Williams

4
为什么这些方法应该是静态的?只需要在Voxel类中声明这些方法为 virtual bool hasMaterial();virtual bool hasDensity();,并在任何子类中覆盖它们即可。如果子类拥有这些方法,则返回true,否则返回false。
然后可以这样做:
void process(Voxel* voxel)
{
    if(voxel->hasMaterial())
    {
        //Do some work which involves calling get/setMaterial()
    }
    if(voxel->hasDensity())
    {
        //Do some work which involves calling get/setDensity()
    }
}

然后,您可以创建一个类似于接口的类,其中包含材料和密度的getter和setter,并让它们继承该类。

1
相对于静态成员或类型特征,这会产生额外的运行时开销。 - Björn Pollex
1
是的,但哪里说他想要完全优化? - Léon Rodenburg
1
谢谢您的评论,但我希望有一个编译时的解决方案,这样理想情况下,冗余的if语句可以被编译器移除。在这种情况下,性能很重要。我已经更新了代码,以展示在我的代码中使用模板的方法。 - David Williams

2

静态成员无法被覆盖。您应该将它们改为虚拟的。我猜您可能在设计方面有问题,请尽可能粘贴一些UML图或源代码。


我之前不知道这个限制。我知道静态成员不能是虚拟的,但我不知道你根本不能覆盖它们。我会进一步测试和研究这个问题。 - David Williams
好的,事实证明我想说的是“隐藏”而不是“覆盖”。子类可以通过重新定义静态函数来隐藏基类中的静态函数。 - David Williams
1
我建议你避免隐藏方法,这可能会在未来产生很多问题。我仍然相信你的设计存在某种问题。在这种情况下,UML图或源代码可能有助于解决你的问题。 - AlexTheo

2

不要使用运行时,而要使用静态检查。

首先,定义这个宏:

#define HAS_MEMBER_VARIABLE( NEW_STRUCT, VAR )                                  \
template<typename T> struct NEW_STRUCT {                                        \
    struct Fallback { int VAR; }; /* introduce member name "VAR" */             \
    struct Derived : T, Fallback { };                                           \
                                                                                \
    template<typename C, C> struct ChT;                                         \
                                                                                \
    template<typename C> static char (&f(ChT<int Fallback::*, &C::VAR>*))[1];   \
    template<typename C> static char (&f(...))[2];                              \
                                                                                \
    static bool const value = sizeof(f<Derived>(0)) == 2;                       \
};

这段代码用于检查类中是否存在成员变量。

如果该变量存在,可以使用SFINAE执行某些操作,就像下一个示例中所示:

#include <iostream>

#define HAS_MEMBER_VARIABLE( NEW_STRUCT, VAR )                                  \
template<typename T> struct NEW_STRUCT {                                        \
    struct Fallback { int VAR; }; /* introduce member name "VAR" */             \
    struct Derived : T, Fallback { };                                           \
                                                                                \
    template<typename C, C> struct ChT;                                         \
                                                                                \
    template<typename C> static char (&f(ChT<int Fallback::*, &C::VAR>*))[1];   \
    template<typename C> static char (&f(...))[2];                              \
                                                                                \
    static bool const value = sizeof(f<Derived>(0)) == 2;                       \
};

HAS_MEMBER_VARIABLE( x_check, x )

struct A
{
    int x;
};
struct B
{
    float notX;
};

template< typename T, bool hasX = x_check<T>::value >
struct doX
{
    static void foo( const T & t )
    {
        std::cout<<"type has x variable, and it's value is "<<t.x<<std::endl;
    }
};
template< typename T >
struct doX< T, false >
{
    static void foo( const T & )
    {
        std::cout<<"type has no x variable"<<std::endl;
    }
};

template< typename T >
void doFoo( const T& t )
{
    doX< T, x_check<T>::value >::foo( t );
};

int main()
{
    A a;    a.x = 6;
    B b;    b.notX = 3.6;

    std::cout<<"Calling foo() on A : ";
    doFoo( a );
    std::cout<<"Calling foo() on B : ";
    doFoo( b );
}

这是一个不错的答案,但我已经说明了我知道基于SFINAE的方法,并会单独考虑它们。我会标记为有用,但不能接受为正确答案。 - David Williams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接