派生类构造函数中的数据驱动标志设置

Data-driven flag setting in derived class constructor

提问人:Luchian Grigore 提问时间:2/4/2013 更新时间:2/4/2013 访问量:625

问:

假设我有一个基类,里面有一个派生类必须设置的标志:

struct Base
{
    bool flag;
    Base(bool flag):flag(flag) {}
};

我想配置哪些派生类以数据驱动的方式将标志设置为 / - 即我想从标头配置它。truefalse

struct Derived1 : Base
{
    Derived1() : Base( expr ) {}
};

哪里有东西(还不知道是什么)能够从标题中获取信息 - 判断是否应该为真或假。理想情况下,如果我创建一个新的派生类但未能在标头中指定标志,我会收到错误,但这不是强制性的。这样,我只需修改单个中心位置即可进行更改。exprDerived1flag

对此的惯用方法是什么?

C++ 设计模式 数据驱动

评论

1赞 Gearoid Murphy 2/4/2013
为什么常量模板参数不起作用?
0赞 sehe 2/4/2013
@LuchianGrigore 我想 maaaayybe 如果您在派生的字段上声明了字段并初始化它,您可以读取它并带有特征 empling - 这是一个非常长的镜头。constexprdeclval<T>()

答:

2赞 Lightness Races in Orbit 2/4/2013 #1

你可以这样写:

struct Derived1 : Base
{
    Derived1() : Base(Concept<Derived1>::theFlag) {}
};

您的标头可以包含以下内容:

template <typename T>
struct Concept
{};

template <>
struct Concept<Derived1>
{
   static const bool theFlag = true;
};

对每种类型重复专业化。

你是这个意思吗?当您没有为某些 .DerivedN

评论

0赞 Gearoid Murphy 2/4/2013
也许它是一个运行时参数?
0赞 Lightness Races in Orbit 2/4/2013
@Gearoid:在运行时无法决定标头的内容。
0赞 Luchian Grigore 2/4/2013
@GearoidMurphy没有。现在是编译时。@轻盈是的,与此类似,但我想要一个更紧凑的版本。
0赞 Lightness Races in Orbit 2/4/2013
@LuchianGrigore:我不确定我能不能打败它。C++ 没有任何类型的本机数据交换格式。
0赞 Gearoid Murphy 2/4/2013
枚举的常量可以达到同样的效果吗?
2赞 Lightness Races in Orbit 2/4/2013 #2

使用单个函数的替代版本可能更紧凑:

struct Derived1 : Base
{
    Derived1() : Base(theFlag(this)) {}
};

然后在标题中:

template <typename T>
bool theFlag(T*)
{
   if (typeid(T) == typeid(Derived1)) return true;
   if (typeid(T) == typeid(Derived2)) return false;
   if (typeid(T) == typeid(Derived3)) return true;

   throw std::runtime_error("No theFlag is given for this type");
}

如果你已经习惯了编译时检查,你能做的最好的事情就是引入一些重复:

template <typename T>
bool theFlag(T*)
{
   static_assert(
      std::is_same<T, Derived1>::value ||
      std::is_same<T, Derived2>::value ||
      std::is_same<T, Derived3>::value,
      "No theFlag is given for this type"
   );

   if (typeid(T) == typeid(Derived1)) return true;
   if (typeid(T) == typeid(Derived2)) return false;
   if (typeid(T) == typeid(Derived3)) return true;
}

这基本上依赖于 SFINAE - 如果您使用不受支持的参数调用它,编译器将无法找到重载。theFlag

评论

0赞 Luchian Grigore 2/4/2013
嗯,这个看起来更好,是的。
0赞 Lightness Races in Orbit 2/4/2013
我不相信这是对的。也就是说,我敢肯定不是。也许有人可以在我最初的 5 分钟结束之前帮助我纠正它?:Pstatic_assert
0赞 Puppy 2/4/2013
这是完全错误的。要么在编译时通过模板专用化执行类型调度,在这种情况下可以static_assert,要么断言将始终失败。
0赞 Lightness Races in Orbit 2/4/2013
@DeadMG:是的,我不认为我能进一步改进这个问题,除非最终回到另一个答案。
1赞 Gearoid Murphy 2/4/2013 #3

下面是一个纯编译时解决方案:

struct Derived1 ;
struct Derived2 ;
template <typename Derived> struct Bootstrap
{
    bool init(Derived1 *) { return true ; }
    bool init(Derived2 *) { return false ; }
    Bootstrap():flag(init(static_cast<Derived *>(this))){}
    bool flag ;
};
struct Derived1: public Bootstrap <Derived1> {};
struct Derived2: public Bootstrap <Derived2> {};
int main()
{
    Derived1 d1 ;
    Derived2 d2 ;
    std::cout<<d1.flag<<" "<<d2.flag<<std::endl ;
    return 0 ;
}

编辑

正如“轨道上的轻度竞赛”所指出的那样,在ctor过程中static_cast会导致不明确的Behabiour(UB)。下面是一个更新的实现,它否定了对 static_cast 运算符的需求:

#include <iostream>
struct Derived1 ;
struct Derived2 ;
namespace BootstrapDetail
{
    template <typename Identifier> bool init();
    template <> bool init <Derived1>() { return true ; }
    template <> bool init <Derived2>() { return false ; }
}
template <typename Derived> struct Bootstrap
{
    Bootstrap(): flag(BootstrapDetail::init<Derived>()) {}
    bool flag ;
};
struct Derived1: public Bootstrap <Derived1> {};
struct Derived2: public Bootstrap <Derived2> {};
int main()
{
    Derived1 d1 ;
    Derived2 d2 ;
    std::cout<<d1.flag<<" "<<d2.flag<<std::endl ;
    return 0 ;
}

评论

0赞 Xeo 2/4/2013
struct Derived1*咳嗽struct Derived2*
0赞 Gearoid Murphy 2/4/2013
在 C++ 中是否需要遵循该约定?
3赞 Xeo 2/4/2013
不,但它使代码更短。:)它实际上是一个内联声明,这意味着您不再需要顶部的 and。struct Derived1;struct Derived2;
0赞 Gearoid Murphy 2/4/2013
太棒了,我不知道。它也适用于“类”结构吗?
2赞 Lightness Races in Orbit 2/4/2013
@GearoidMurphy:不,在初始化期间,某些静态强制转换也会受到限制(例如,参见 12.7/3)。虽然我找不到任何禁止你的代码的措辞。
2赞 jrok 2/4/2013 #4

我会为标志编写一个特征类,并使用宏来定义专业化:

#include <type_traits>

template<typename T>
struct FlagTrait {
    static_assert(std::is_void<T>::value, "No flag defined for this type.");
};

#define DEFINE_FLAG(Type, Val)               \
        template<>                           \
        struct FlagTrait<class Type>         \
            : std::integral_constant<bool, Val> {};

template<typename T>
constexpr bool getFlag(T) { return FlagTrait<T>::value; }

#define GET_FLAG getFlag(*this)

现在,对于每个新的派生类,我们需要做的就是添加一行带有类名和标志值的行:

DEFINE_FLAG(Derived1, true)
DEFINE_FLAG(Derived2, false)

用法:

struct Base
{
    bool flag;
    Base(bool flag):flag(flag) {}
};

struct Derived1 : Base
{
    Derived1() : Base(GET_FLAG) {}
};

struct Derived2 : Base
{
     Derived2() : Base(GET_FLAG) {}
};