【问题标题】:Generic switch statement to initialize derived types用于初始化派生类型的通用 switch 语句
【发布时间】:2012-06-27 10:08:11
【问题描述】:

我正在创建一个 DNS 名称解析器。在解析返回的数据包时,我读取了 RR 类型,然后将其传递给 switch 以初始化类 Record 的派生类型。记录类型是一个大型枚举类型。我使用模板专业化将枚举映射到结构。

template < QueryType n >
struct Struct;

template <> struct Struct< DNS_Q_A > { typedef A Type; };
template <> struct Struct< DNS_Q_CNAME > { typedef CNAME Type; };

template < QueryType n > struct Struct { typedef UNKNOWN Type; };

目前我有 4 个 switch 语句,它们都做了非常相似的事情。即调用 operator new,placement new,用于复制和移动 c'tors。这是需要维护的大量代码。我希望只有 1 个 switch 语句,我可以在其中传递某种类型的对象,其中包含要执行的函数、返回类型和 n 个参数。

switch语句如下:

switch ( nType )
{
case DNS_Q_A:
    pInstance = new ( &Container ) Struct< DNS_Q_A >::Type( dynamic_cast< const Struct< DNS_Q_A >::Type& >( Other ) );
    break;
case DNS_Q_CNAME:
    pInstance = new ( &Container ) Struct< DNS_Q_CNAME >::Type( dynamic_cast< const Struct< DNS_Q_CNAME >::Type& >( Other ) );
    break;
}

如您所见,除了对结构类型的依赖之外,每种情况都是相同的。这对我来说是“模板”,但我不知道如何传入一个对象。

我仅限于编码 4 个开关还是有办法?请不要引用“Boost”,此代码必须独立于任何其他库。

解决方案:(感谢 Jan Hudec)

template< template < class > class Action >
typename Action< Struct< DNS_Q_A >::Type >::result_type
    CreateRecord ( 
        unsigned n,
        typename Action< Struct< DNS_Q_A >::Type >::first_argument_type arg1,
        typename Action< Struct< DNS_Q_A >::Type >::second_argument_type arg2 )
{
    typedef typename typename Action< Struct< DNS_Q_A >::Type >::result_type ReturnType;
    switch ( n )
    {
    case DNS_Q_A:       return static_cast< ReturnType >( Action< Struct< DNS_Q_A >::Type >()( arg1, arg2 ) );
    case DNS_Q_NS:      return static_cast< ReturnType >( Action< Struct< DNS_Q_NS >::Type >()( arg1, arg2 ) );
    /*...*/
    }
}

Action 结构定义为:

template < typename T >
struct Copy : std::binary_function< storage_type&, const Record&, Record* >
{
    Record* operator() ( storage_type& Storage, const Record& Obj )
    {
        return new ( &Storage ) T( dynamic_cast< const T& >( Obj ) );
    }
};
template < typename T >
struct Move : std::binary_function< storage_type&, Record&&, Record* >
{
    Record* operator() ( storage_type& Storage, const Record& Obj )
    {
        return new ( &Storage ) T( dynamic_cast< const T& >( std::move( Obj ) ) );
    }
};

并将 Switch 语句替换为:

pInstance = CreateRecord< Copy >( nType, Container, Other );

【问题讨论】:

  • 你的代码可以使用 C++11 特性吗?特别是可变参数模板会使事情更容易使用。
  • 我用 VS2010 限制了 C++11,不幸的是不支持可变参数模板。
  • 嗯,你可以从模板开始 BaseType* doit(ContainerType &container, BaseType &other) { return new ( &container ) Struct::Type( dynamic_cast ::类型& >(其他)); },然后在该函数模板周围的包装器中编写调度。 (我假设 A、CNAME 等都是某个基类的子类,而 Other 是该基类的一个实例?)
  • A、CNAME等都是派生自抽象类Record。 Other 是派生类型或基类型的实例。 c'tors 处理这些情况以及类型不匹配。我从你的建议开始,但是 Switch() 函数需要一个带有虚拟成员的通用基本类型,并且虚拟成员不能是模板。
  • 好吧,我就是这么想的。看起来 Jan Hudec 正在写答案,所以我会等待而不是重复努力。

标签: c++ templates polymorphism


【解决方案1】:

我必须承认我不太明白这里不能通过...愚蠢的虚函数来完成什么。

class Object {
public:
    virtual Object* clone() const = 0;
    virtual void clone(Container& c) const = 0;

    // ...
};


class A: public Object {
public:
    virtual A* clone() const override { return new A(*this); }
    virtual void clone(Container& c) const override { new (&c) A(*this); }

    // ...
};

那么……只要去掉开关:

pInstance = other.clone(container);

任务完成了。

【讨论】:

  • 我已经实现了复制。那不是问题。在某些时候,必须创建实际的 A/CNAME 记录,因为必须知道类型。除了 A/CNAME 记录外,我还有一个 ANY 记录,它使用新位置进行初始化。同样,必须知道记录类型。因此有 4 个大型 switch 语句。
  • @Waldermort:那你为什么要谈论复制和移动构造函数?那些已经存在的对象,因此可以使用虚拟方法;你的例子也是关于一个已经存在的对象。在我看来,您只需要 one 开关:用于初始构造的开关,不是吗?
  • 每个拷贝构造函数、移动构造函数、放置拷贝构造函数和放置新构造函数都有一个开关。在我最初的问题中明确说明。所有这些都初始化派生类型。因此,问题是,我怎样才能用一个开关做到这一点?
  • @Waldermort:我的回答是:不需要切换来复制或移动。由于您已经有一个派生类型的实例,一个简单的virtual 调用就足够了。唯一需要切换的时刻是初始构造。
  • 抱歉,我没有意识到英语不是您的第一语言。请允许我重复一遍。每个 switch 初始化,调用所有四种形式的 new,调用一个构造函数。 operator new 除了分配内存和/或调用构造函数之外什么也不做。您提出的是 operator=() 分配一个值而不是构造它。
【解决方案2】:

您可以创建一个函数模板来执行函子模板。如果没有 C++11 可变参数,所有仿函数都需要相同数量的参数,因此您必须将它们打包在一个结构中,或者在它们不相关时传递 NULL。使用可变参数模板(我还没有使用它们,所以我不记得确切的语法并且不会在这里写它们)您可以轻松地在每种形式中使用不同的参数。

这个想法是这样的(在我的脑海中,所以可能有一些错别字):

template <template <typename T> class F>
F<Struct<DNS_Q_A>::Type>::return_type RecordCall(RecordType nType, const F<Struct<DNS_Q_A>::Type>::argument_type &arg)
{
    switch(nType)
    {
        case DNS_Q_A:
            return F<Struct<DNSK_Q_A>::Type>()(arg);
        case DNS_Q_CNAME:
            return F<Struct<DNSK_Q_CNAME>::Type>()(arg);
        // ...
     }
}

现在你编写如下的单个函数:

template <typename T>
struct Clone : std::unary_function<BaseType *, const BaseType *>
{
    BaseType *operator()(const BaseType *source)
    {
        return new<T>(dynamic_cast<const BaseType &>(*source));
    }
}

并结合在一起:

target = RecordCall<Clone>(source->GetType(), source);

(并包装在另一个函数中,该函数将为多参数形式插入 getter 和/或打包参数,例如放置副本构造)

虽然复制的常用方法是使用虚拟克隆成员方法。但这不适用于建筑。

编辑:请注意,我将内部模板中的返回和参数类型定义为 typedef(使用标准的 unary_function 助手),但它们也可以作为单独的模板参数传递,尤其是在使用 RecordCall 时将被包装在另一个函数中。也许返回类型甚至不必是模板参数,因为对于问题中提到的所有情况,它都将是 Record *

【讨论】:

  • 这与我第一次尝试的惊人相似。我一定走在正确的轨道上,但是返回类型和函数参数让我走错了路。我在上面看到的一个问题是返回类型,总是 A*。我确信我可以通过返回基本记录 * 或​​根本不返回而侥幸。今晚我将对其进行编码并更新它的进展情况。谢谢。
  • @Waldermort:对于内部函子的所有特化,参数和返回类型都必须相同,因为外部模板仅在运行时知道它将是哪个特定子类型。所以你必须使用那里的基地。
  • 很抱歉没有提前标记您的答案。我想先让它工作。现在,它完全按照我的希望工作。我之前在参数方面遇到了麻烦。您对使用 std::unary_function 的建议以及 google 的示例将我推向了正确的方向。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-10
  • 1970-01-01
  • 2020-08-19
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多