代码之家  ›  专栏  ›  技术社区  ›  Mordachai

如何专门化用于数据类型分类的模板类?

  •  1
  • Mordachai  · 技术社区  · 16 年前

    我们使用Boost——所以使用那个库应该没问题。

    但是我从来没有想过要创建一组模板来为整个数据类型类提供正确的专门化,而不是专门化为单个数据类型(我知道如何做)。

    让我举个例子,试着把这个带到地球上来。我想要一组类,可以用作:

    Initialized<T> t;
    

    其中t是简单的基本类型、pods或数组。它不能是一个类,因为一个类应该有自己的构造函数,覆盖它的原始内存是一个糟糕的主意。

    初始化基本上应该是memset(&t,0,sizeof(t));这样在处理遗留结构时,可以更容易地确保运行时代码与调试代码不存在差异。

    在sdt=simple数据类型的地方初始化,应该简单地创建一个结构,该结构包装基础sdt,并使用编译器t()为该类型生成编译器定义的默认构造函数(它也可以是一个memset,不过简单地生成t()似乎更为优雅)。

    这是一个尝试,对pods使用initialized<gt;,对sdts使用initialized<gt;:

    // zeroed out PODS (not array)
    // usage:  Initialized<RECT> r;
    template <typename T>
    struct Initialized : public T
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
    
        // publish our underlying data type
        typedef T DataType;
    
        // default (initialized) ctor
        Initialized() { Reset(); }
    
        // reset
        void Reset() { Zero((T&)(*this)); }
    
        // auto-conversion ctor
        template <typename OtherType> Initialized(const OtherType & t) : T(t) { }
    
        // auto-conversion assignment
        template <typename OtherType> Initialized<DataType> & operator = (const OtherType & t) { *this = t; }
    };
    

    SDTs:

    // Initialised for simple data types - results in compiler generated default ctor
    template <typename T>
    struct Initialised
    {
        // default valued construction
        Initialised() : m_value() { }
    
        // implicit valued construction (auto-conversion)
        template <typename U> Initialised(const U & rhs) : m_value(rhs) { }
    
        // assignment
        template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }
    
        // implicit conversion to the underlying type
        operator T & () { return m_value; }
        operator const T & () const { return m_value; }
    
        // the data
        T   m_value;
    };
    

    我已经为t*进行了专门的初始化,以提供自然的指针行为。对于数组,我有一个initializedarray<gt;,它将元素类型和数组大小作为模板参数。但同样,我必须使用模板名来区分-我没有足够好地搜索MPL,以提供一个在编译时导致正确专门化的单个模板,所有这些都是从单个名称(理想情况下是初始化的<gt;)开始的。

    我还希望能够提供重载的初始化类型名t、t init_value>,以便用户可以为非标量值定义默认的初始化值(或memset value)。

    我很抱歉问了一些可能需要努力回答的问题。这似乎是我在自己的MPL阅读中无法克服的一个障碍,但也许在您的帮助下,我可以确定这个功能!


    根据本叔叔下面的回答,我尝试了以下方法:

    // containment implementation
    template <typename T, bool bIsInheritable = false>
    struct InitializedImpl
    {
        // publish our underlying data type
        typedef T DataType;
    
        // auto-zero construction
        InitializedImpl() : m_value() { }
    
        // auto-conversion constructor
        template <typename U> InitializedImpl(const U & rhs) : m_value(rhs) { }
    
        // auto-conversion assignment
        template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }
    
        // implicit conversion to the underlying type
        operator T & () { return m_value; }
        operator const T & () const { return m_value; }
    
        // the data
        T   m_value;
    };
    
    // inheritance implementation
    template <typename T>
    struct InitializedImpl<T,true> : public T
    {
        // publish our underlying data type
        typedef T DataType;
    
        // auto-zero ctor
        InitializedImpl() : T() { }
    
        // auto-conversion ctor
        template <typename OtherType> InitializedImpl(const OtherType & t) : T(t) { }
    
        // auto-conversion assignment
        template <typename OtherType> InitializedImpl<DataType> & operator = (const OtherType & t) { *this = t; }
    };
    
    // attempt to use type-traits to select the correct implementation for T
    template <typename T>
    struct Initialized : public InitializedImpl<T, boost::is_class<T>::value>
    {
        // publish our underlying data type
        typedef T DataType;
    };
    

    然后尝试了一些使用测试。

    int main()
    {
        Initialized<int> i;
        ASSERT(i == 0);
        i = 9;  // <- ERROR
    }
    

    这将导致错误:*binary“=”:找不到接受“initializedimpl”类型的右侧操作数的运算符 '(或者没有可接受的转换)

    但是,如果我直接实例化正确的基类型(而不是派生类型):

    int main()
    {
        InitializedImpl<int,false> i;
        ASSERT(i == 0);
        i = 9;  // <- OK
    }
    

    现在我可以把我当作任何一个旧的内景,这就是我想要的!

    如果我尝试对结构执行相同的操作,则会出现完全相同的问题:

    int main()
    {
        Initialized<RECT> r;
        ASSERT(r.left == 0);  // <- it does let me access r's members correctly! :)
    
        RECT r1;
        r = r1;  // <- ERROR
    
        InitializedImpl<RECT,true> r2;
        r2 = r1; // OK
    }
    

    所以,正如您所看到的,我需要一些方法来告诉编译器将一个初始化函数升级为一个真正的t。

    如果C++让我从基本类型继承,我可以只使用继承技术,一切都会很好。

    或者,如果我有办法告诉编译器将父代中的所有方法都外推到子代,以便父代中的任何有效方法都对子代有效,我会没事的。

    或者,如果我可以使用MPL或类型特征来typedef而不是继承我需要的东西,那么就没有子类,也没有传播问题。

    思想?!

    3 回复  |  直到 14 年前
        1
  •  2
  •   UncleBens    16 年前

    初始化应该基本上 memset(&t,0,sizeof(t));它使 更容易确保运行时代码 与调试代码没有区别 处理遗留结构。

    我认为您不需要memset,因为您可以对pods进行零初始化,就像显式调用非pods的默认构造函数一样。(除非我大错特错)。

    #include <cassert>
    
    struct X {int a, b; };
    
    template <typename T>
    struct Initialized
    {
        T t;
    
        // default (initialized) ctor
        Initialized(): t()  { }
    
    };
    
    template <typename T>
    struct WithInheritance: public T
    {
        // default (initialized) ctor
        WithInheritance(): T()  { }
    };
    
    int main()
    {
        Initialized<X> a;
        assert(a.t.a == 0 && a.t.b == 0);
    
        //it would probably be more reasonable not to support arrays,
        //and use boost::array / std::tr1::array instead
        Initialized<int[2]> b;
        assert(b.t[0] == 0 && b.t[1] == 0);
    
        WithInheritance<X> c;
        assert(c.a == 0 && c.b == 0);
    }
    

    在确定类型的pod ness的过程中,您可能还考虑了boost::is_pod reference:

    没有任何帮助 从编译器中,is-pod将永远不会 报告类或结构是 如果可能的话,这总是安全的。 次优的目前(2005年5月)仅限 MWCW 9和Visual C++ 8都有 必要的编译器内部函数。

    (我想Boo:):Type的特性使它成为C++中的标准库,在这种情况下,期待一个 is_pod 这确实有效。)


    但是,如果希望基于条件专门化,可以引入一个bool参数。例如:

    #include <limits>
    #include <cstdio>
    
    template <class T, bool b>
    struct SignedUnsignedAux
    {
        void foo() const { puts("unsigned"); }
    };
    
    template <class T>
    struct SignedUnsignedAux<T, true>
    {
        void foo() const { puts("signed"); }
    };
    
    //using a more reliable condition for an example
    template <class T>
    struct SignedUnsigned: SignedUnsignedAux<T, std::numeric_limits<T>::is_signed > {};
    
    int main()
    {
        SignedUnsigned<int> i;
        SignedUnsigned<unsigned char> uc;
        i.foo();
        uc.foo();
    }
    

    这里还有一些类似于您想象的东西(至少使用mingw 4.4和vc++2005编译,后者也很好地生成了 警告 数组将被零初始化!:)

    这使用了一个默认的布尔参数,您可能永远都不应该指定自己。

    #include <boost/type_traits.hpp>
    #include <iostream>
    
    template <class T, bool B = boost::is_scalar<T>::value>
    struct Initialized
    {
        T value;
        Initialized(const T& value = T()): value(value) {}
        operator T& () { return value; }
        operator const T& () const { return value; }
    };
    
    template <class T>
    struct Initialized<T, false>: public T
    {
        Initialized(const T& value = T()): T(value) {}
    };
    
    template <class T, size_t N>
    struct Initialized<T[N], false>
    {
        T array[N];
        Initialized(): array() {}
        operator T* () { return array; }
        operator const T* () const { return array; }
    };
    
    //some code to exercise it
    
    struct X
    {
        void foo() const { std::cout << "X::foo()" << '\n'; }
    };
    
    void print_array(const int* p, size_t size)
    {
        for (size_t i = 0; i != size; ++i) {
            std::cout << p[i] <<  ' ';
        }
        std::cout << '\n';
    }
    
    template <class T>
    void add_one(T* p, size_t size)
    {
        for (size_t i = 0; i != size; ++i) {
            p[i] += T(1);
        }
    }
    
    int main()
    {
        Initialized<int> a, b = 10;
        a = b + 20;
        std::cout << a << '\n';
        Initialized<X> x;
        x.foo();
        Initialized<int[10]> arr /*= {1, 2, 3, 4, 5}*/; //but array initializer will be unavailable
        arr[6] = 42;
        add_one<int>(arr, 10);  //but template type deduction fails
        print_array(arr, 10);
    }
    

    然而,初始化可能永远不会像真实的那样好。测试代码中显示了一个缺点:它会干扰模板类型的推导。另外,对于数组,您还有一个选择:如果您想用构造函数对其进行零初始化,那么就不能进行非默认的数组初始化。

    如果用法是跟踪所有未初始化的变量并将其包装为已初始化的变量,那么我不太确定为什么不自己初始化它们。

    此外,对于跟踪未初始化的变量,编译器警告可能会有很大帮助。

        2
  •  0
  •   James    16 年前

    我知道它不能回答你的问题,但我认为POD结构总是零初始化的。

        3
  •  0
  •   Mordachai    16 年前

    由于我能够使用UncleBen的答案来创建一个全面的解决方案(就像我认为它在C++中得到的那样好),所以我想分享它,如下:

    你可以随意使用它,但我不保证它对任何用途等的价值,作为一个成年人,对你自己的行为负责,等等:

    //////////////////////////////////////////////////////////////
    // Raw Memory Initialization Helpers
    //
    //  Provides:
    //      Zero(x) where x is any type, and is completely overwritten by null bytes (0).
    //      Initialized<T> x; where T is any legacy type, and it is completely null'd before use.
    //
    // History:
    //
    //  User UncleBen of stackoverflow.com and I tried to come up with 
    //  an improved, integrated approach to Initialized<>
    //  http://stackoverflow.com/questions/2238197/how-do-i-specialize-a-templated-class-for-data-type-classification
    //
    //  In the end, there are simply some limitations to using this
    //  approach, which makes it... limited.
    //
    //  For the time being, I have integrated them as best I can
    //  However, it is best to simply use this feature
    //  for legacy structs and not much else.
    //
    //  So I recommend stacked based usage for legacy structs in particular:
    //      Initialized<BITMAP> bm;
    //
    //  And perhaps some very limited use legacy arrays:
    //      Initialized<TCHAR[MAX_PATH]> filename;
    //
    //  But I would discourage their use for member variables:
    //      Initialized<size_t> m_cbLength;
    //  ...as this can defeat template type deduction for such types 
    //  (its not a size_t, but an Initialized<size_t> - different types!)
    //
    //////////////////////////////////////////////////////////////
    
    #pragma once
    
    // boost
    #include <boost/static_assert.hpp>
    #include <boost/type_traits.hpp>
    
    // zero the memory space of a given PODS or native array
    template <typename T>
    void Zero(T & object, int zero_value = 0)
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
    
        // make zeroing out a raw pointer illegal
        BOOST_STATIC_ASSERT(!(boost::is_pointer<T>::value));
    
        ::memset(&object, zero_value, sizeof(object));
    }
    
    // version for simple arrays
    template <typename T, size_t N>
    void Zero(T (&object)[N], int zero_value = 0)
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
    
        ::memset(&object, zero_value, sizeof(object));
    }
    
    // version for dynamically allocated memory
    template <typename T>
    void Zero(T * object, size_t size, int zero_value = 0)
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
    
        ::memset(object, zero_value, size);
    }
    
    //////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////
    
    
    //////////////////////////////////////////////////////////////////////////
    // Initialized for non-inheritable types
    // usage: Initialized<int> i;
    template <typename T, bool SCALAR = boost::is_scalar<T>::value>
    struct Initialized
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_scalar<T>::value));
    
        // the data
        T   m_value;
    
        // default valued construction
        Initialized() : m_value() { }
    
        // implicit valued construction (auto-conversion)
        template <typename U> Initialized(const U & rhs) : m_value(rhs) { }
    
        // assignment
        template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }
    
        // implicit conversion to the underlying type
        operator T & () { return m_value; }
        operator const T & () const { return m_value; }
    
        // zero method for this type
        void _zero() { m_value = T(); }
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Initialized for inheritable types (e.g. structs)
    // usage:  Initialized<RECT> r;
    template <typename T>
    struct Initialized<T, false> : public T
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
    
        // default ctor
        Initialized() : T() {  }
    
        // auto-conversion ctor
        template <typename OtherType> Initialized(const OtherType & value) : T(value) { }
    
        // auto-conversion assignment
        template <typename OtherType> Initialized & operator = (const OtherType & value) { *this = value; }
    
        // zero method for this type
        void _zero() { Zero((T&)(*this)); }
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Initialized arrays of simple types
    // usage: Initialized<char, MAXFILENAME> szFilename;
    template <typename T, size_t N>
    struct Initialized<T[N],false>
    {
        // ensure that we aren't trying to overwrite a non-trivial class
        BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
    
        // internal data
        T m_array[N];
    
        // default ctor
        //Initialized() : m_array() { } // Generates a warning about new behavior.  Its okay, but might as well not produce a warning.
        Initialized() { Zero(m_array); }
    
        // array access
        operator T * () { return m_array; }
        operator const T * () const { return m_array; }
    
        // NOTE: All of the following techniques leads to ambiguity.
        //       Sadly, allowing the type to convert to ArrayType&, which IMO should
        //       make it fully "the same as it was without this wrapper" instead causes
        //       massive confusion for the compiler (it doesn't understand IA + offset, IA[offset], etc.)
        //       So in the end, the only thing that truly gives the most bang for the buck is T * conversion.
        //       This means that we cannot really use this for <char> very well, but that's a fairly small loss
        //       (there are lots of ways of handling character strings already)
    
        //  // automatic conversions
        //  operator ArrayType& () { return m_array; }
        //  operator const ArrayType& () const { return m_array; }
        // 
        //  T * operator + (long offset) { return m_array + offset; }
        //  const T * operator + (long offset) const { return m_array + offset; }
        // 
        //  T & operator [] (long offset) { return m_array[offset]; }
        //  const T & operator [] (long offset) const { return m_array[offset]; }
    
        // metadata
        size_t GetCapacity() const { return N; }
    
        // zero method for this type
        void _zero() { Zero(m_array); }
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Initialized for pointers to simple types
    // usage: Initialized<char*> p;
    // Please use a real smart pointer (such as std::auto_ptr or boost::shared_ptr)
    //  instead of this template whenever possible.  This is really a stop-gap for legacy
    //  code, not a comprehensive solution.
    template <typename T>
    struct Initialized<T*, true>
    {
        // the pointer
        T * m_pointer;
    
        // default valued construction
        Initialized() : m_pointer(NULL) { }
    
        // valued construction (auto-conversion)
        template <typename U> Initialized(const U * rhs) : m_pointer(rhs) { }
    
        // assignment
        template <typename U> T * & operator = (U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
        template <typename U> T * & operator = (const U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
    
        // implicit conversion to underlying type
        operator T * & () { return m_pointer; }
        operator const T * & () const { return m_pointer; }
    
        // pointer semantics
        const T * operator -> () const { return m_pointer; }
        T * operator -> () { return m_pointer; }
        const T & operator * () const { return *m_pointer; }
        T & operator * () { return *m_pointer; }
    
        // allow null assignment
    private:
        class Dummy {};
    public:
        // amazingly, this appears to work.  The compiler finds that Initialized<T*> p = NULL to match the following definition
        T * & operator = (Dummy * value) { m_pointer = NULL; ASSERT(value == NULL); return *this; }
    
        // zero method for this type
        void _zero() { m_pointer = NULL; }
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Uninitialized<T> requires that you explicitly initialize it when you delcare it (or in the owner object's ctor)
    //  it has no default ctor - so you *must* supply an initial value.
    template <typename T>
    struct Uninitialized
    {
        // valued initialization
        Uninitialized(T initial_value) : m_value(initial_value) { }
    
        // valued initialization from convertible types
        template <typename U> Uninitialized(const U & initial_value) : m_value(initial_value) { }
    
        // assignment
        template <typename U> T & operator = (const U & rhs) { if (&m_value != &rhs) m_value = rhs; return *this; }
    
        // implicit conversion to underlying type
        operator T & () { return m_value; }
        operator const T & () const { return m_value; }
    
        // the data
        T   m_value;
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Zero() overload for Initialized<>
    //////////////////////////////////////////////////////////////////////////
    
    // version for Initialized<T>
    template <typename T, bool B>
    void Zero(Initialized<T,B> & object)
    {
        object._zero();
    }
    
    推荐文章