继承(或成员)特征成语

Inheriting (or member) traits idiom

本文关键字:特征 成语 成员 继承      更新时间:2023-10-16

std::iterator_traits这样的包罗万象的特征类通过将类型的属性与其定义分开而很有用,例如,属性可以在定义完成之前可用。

除了每个客户端类本身之外,定义特征是不方便的,因为这些特征通常也作为成员占有一席之地。这就是为什么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 > {
    ...
};

(为了论证起见,我们忽略一个事实,即必须在namespace std中声明实际的std::iterator_traits专业化。我正在尝试对用户代码中可能发生的事情进行熟悉的插图。

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

从概念上讲,最好将类的质量与其服务的实现分开,无论这种分离是否必要。但是,这种风格确实需要将每个客户端类分成两部分,包括明确的专业化,这有点丑陋。


有人熟悉这个设计空间吗?我倾向于第二个成语,尽管它在实践中看起来很不寻常。但是,那些以前在这里走过的人可能知道一些来龙去脉。

用户定义的特征作为库类型的专用化的问题在于库类型属于库。定义显式专用化需要打开库命名空间,这很丑陋。

备选方案 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 >::type T。要将给定类型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

请注意,只要类是可复制和可破坏的,就可以简单地traits_type_entry t const &参数t

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