继承(或成员)特征成语

时间:2013-11-08 04:30:01

标签: c++ design-patterns idioms typetraits

std::iterator_traits这样的Catch-all tr​​aits类通过将类型的属性与其定义分开是有用的,因此例如可以在定义完成之前使属性可用。

除了每个客户端类本身之外定义特征是不方便的,因为特征通常也有作为成员的位置。这就是为什么std::iterator_traits的通用实现是根据其模板参数的成员定义的。

template< typename it >
struct iterator_traits {
    typedef typename it::category category;
    typedef typename it::value_type value_type;
    // etc
};

对于编译器而言,使用继承不是更容易吗?

template< typename t >
struct t_traits : public t {
    t_traits() = delete; // Prevent runtime instances.
};

这无法记录主模板中的界面,但无论如何还有其他机会。

编写大量重复代码来定义元容器类似乎毫无意义,这个习惯用法甚至不能保证在运行时防止这种滥用。


或许这完全是倒退。除std::iterator_traits之外,我们还有std::iterator,一个伪抽象基类,其成员大多相同。这种冗余是代码气味。如果自定义迭代器看起来像这样会不会更好?

template<>
struct iterator_traits< struct my_iterator > {
    typedef random_access_iterator_tag category;
    typedef foo value_type;
    ...
};
struct my_iterator : iterator_traits< struct my_iterator > {
    ...
};

(为了论证,让我们忽略一个事实,即必须在std::iterator_traits中声明一个实际的namespace std专门化。我试图熟悉用户代码中可能发生的事情。 。)

这是更清洁的,因为不需要违反成语来处理任何特殊情况,首先需要花哨的步法。而不是主要特征模板产生内部错误,缺少客户端类不适合某些东西,根本不需要任何主要特征模板。

从概念上讲,将类的质量与其服务的实现分开是更好的,无论这种分离是否必要。但是,这种风格确实需要将每个客户端类分成两部分,包括一个显式的特化,这有点难看。


有人熟悉这个设计空间吗?我倾向于第二个成语,虽然它在实践中看起来很不寻常。但是那些曾经在这里跋涉过的人可能已经知道了这些内容。

1 个答案:

答案 0 :(得分:1)

用户定义的特征作为库类型的特化是一个库类型属于库的问题。定义显式特化需要打开库名称空间,这很难看。

替代品1和2可以组合成两种世界模式中的最佳模式

  • 始终允许关注点的最佳分离(通过将类拆分为特征和实现)
  • 要求拆分类
  • 永远不需要打开库名称空间

需要额外的胶水,以基于ADL的元函数的形式将任何类映射到其特征。

template< typename t >
t traits_type_entry( t const & ); // Declared, never defined.

template< typename t >
using traits_type = decltype( traits_type_entry( std::declval< t >() ) );

默认情况下,T用作自己的特征类型,traits_type< T >::typeT。要将给定类型t更改为特征类t_traits,请声明(但不定义)函数t_traits traits_type_entry( t const & )。此t_traits类可能是也可能不是t的基类; traits_type设施并不关心。因为函数将通过参数依赖查找找到,所以它可以被声明为友元函数,在命名空间范围内没有声明。

嵌套在类中的用法(只是为了制作一个困难的测试用例)看起来像这样。对于命名空间中的常规用法,只需删除friend关键字。

class outer_scope {
    struct special;
    struct special_traits {
        typedef int value_type;
        constexpr static int limit = 5;
    };
    friend special_traits traits_type_entry( special const & );

    struct unspecial {
        typedef double baz_type;
        int table[ util::traits_type< special >::limit ];
    };

    struct special : special_traits {
        void f() {
             std::pair< typename util::traits_type< unspecial >::baz_type,
                        value_type >();
        }
    };
};

http://ideone.com/QztQ6i

注意,t const & traits_type_entry参数可以只是t,只要该类是可复制和可破坏的。

此外,您可以通过让主模板返回从t派生的类型并删除其构造函数而不是t本身来阻止声明(非自定义)特征类型的对象。