2010-02-10 18 views
1

Nous utilisons boost - donc utiliser cette librairie devrait être bon.Comment spécialiser une classe modélisée pour la classification de type de données?

Mais je ne suis jamais parvenu à créer un ensemble de modèles qui vous donne la bonne spécialisation pour toute une classe de types de données, par opposition à la spécialisation pour un seul type de données (que je sais faire)). Laissez-moi aller un exemple pour essayer de ramener cela à la terre. Je veux avoir un ensemble de classes qui peut être utilisé comme:

Initialized<T> t; 

Où T est soit un type de base simple, un PODS, ou un tableau. Cela ne peut pas être une classe, car une classe devrait avoir son propre constructeur, et écraser sa mémoire brute est une idée terrible.

Initialisé devrait essentiellement memset (& t, 0, sizeof (t)); Il est plus facile de s'assurer que le code d'exécution n'est pas différent du code de débogage lorsqu'il traite des structures existantes. Initialisé où SDT = type de données simple, devrait simplement créer une structure qui encapsule le SDT sous-jacent et utilise les compilateurs t() pour générer le constructeur par défaut défini par le compilateur pour ce type (cela peut aussi bien être un memset) il semble plus élégant pour résultat simplement t()

est ici un coup de poignard à elle, en utilisant initialisés <> pour PODs, et initialisés <> pour SDT:.

// 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; } 
}; 

et pour SDT:

// 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; 
}; 

Je me suis spécialisé Initialisé pour T *, pour fournir des comportements de pointeurs naturels. Et j'ai un InitializedArray <> pour les tableaux, qui prend à la fois le type d'élément et la taille de tableau comme arguments de modèle. Mais encore une fois, je dois utiliser le nom du template pour le distinguer - je n'incrémente pas assez MPL pour fournir un template unique qui résulte de la spécialisation correcte à la compilation à partir d'un seul nom (Initialized <>, idéalement).

J'aimerais aussi être en mesure de fournir une surcharge Initialisé < typename T, T init_value > ainsi, de sorte que pour les valeurs non scalaires, l'utilisateur peut définir la valeur d'initialisation par défaut (ou valeur memset)

Je m'excuse d'avoir demandé quelque chose qui demande un peu d'effort pour répondre. Cela semble être un obstacle que je n'ai pas réussi à surmonter seul dans ma propre lecture MPL, mais peut-être qu'avec votre aide, je pourrais peut-être réduire cette fonctionnalité à plus tard!


Sur la base de la réponse de l'oncle Ben (s) ci-dessous, j'ai essayé les éléments suivants:

// 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; 
}; 

Et puis essayé quelques tests d'utilisation.

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

Il en résulte une erreur: * binaire '=': aucun opérateur trouvé qui prend un opérande à droite de type 'InitializedImpl ' (ou il n'y a pas de conversion acceptable)

Alors que si J'instancier directement le type de base correcte (au lieu d'un type dérivé):

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

maintenant, je peux-je utiliser comme tout ancien int. C'est ce que je veux!

exactement les mêmes problèmes se posent si j'essaie de faire la même chose pour struct:

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 
} 

Donc, comme vous pouvez le voir, il me faut une certaine façon de dire au compilateur de promouvoir un initialisées à agir comme un vrai T.

Si C++ me laisse hériter des types de base, je pourrais juste utiliser la technique d'héritage et tout irait bien.

Ou si j'avais un moyen de dire au compilateur d'extrapoler toutes les méthodes de parent à enfant, afin que tout ce qui était valide sur parent soit valide sur l'enfant, ça irait. Ou si je pouvais utiliser MPL ou des caractères de type pour typedef au lieu d'hériter de ce dont j'avais besoin, alors il n'y aurait pas de classe enfant, et pas de problème de propagation.

Idées! ...

+0

Avec trop peu de temps pour avoir un réel Regardez-y: il semble que la spécialisation partielle, peut-être avec l'une ou l'autre utilisation 'boost :: enable_if', devrait faire ce que vous voulez. – gimpf

Répondre

2

Initialized should basically memset(&t, 0, sizeof(t)); It makes it easier to ensure that runtime code is not different from debug code when dealing with legacy structs.

Je ne pense pas que vous avez besoin memset, parce que vous pouvez initialiser zéro PODs comme vous pouvez appeler explicitement le constructeur par défaut de non-pods. (sauf si je me trompe terriblement).

#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); 
} 

Dans votre quête pour déterminer le pod-ness d'un type, vous pouvez également prendre en compte cette note de boost :: référence is_pod:

Without some (as yet unspecified) help from the compiler, is_pod will never report that a class or struct is a POD; this is always safe, if possibly sub-optimal. Currently (May 2005) only MWCW 9 and Visual C++ 8 have the necessary compiler intrinsics.

(je pense boost: : type_traits font dans la bibliothèque standard C++ 0x, et dans un tel cas, il serait raisonnable d'attendre un is_pod qui fonctionne réellement)


.

Mais si vous voulez vous spécialiser en fonction d'une condition, vous pouvez introduire un paramètre booléen. Par exemple, quelque chose comme ceci:

#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(); 
} 

est ici aussi quelque chose qui fonctionne un peu comme vous pourriez imaginer (compile au moins avec MinGW 4.4 et VC++ 2005 - ce dernier produit aussi bien un avertissement que le tableau sera initialisé à zéro! :)).

Ceci utilise un argument booléen par défaut que vous ne devriez probablement jamais vous-même spécifier.

#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); 
} 

Toutefois, l'initialisation ne sera probablement jamais aussi bonne que la réalité. Un court-come est montré dans le code de test: il peut interférer avec la déduction de type de modèle. De plus, pour les tableaux, vous aurez le choix: si vous voulez l'initialiser à zéro avec le constructeur, vous ne pouvez pas avoir d'initialisation de tableau autre que celle par défaut.

Si l'utilisation est que vous allez rechercher toutes les variables non initialisées et les placer dans Initialized, je ne suis pas sûr pourquoi vous ne les initialiserez pas vous-même.

En outre, pour le suivi des variables non initialisées, les avertissements du compilateur peuvent peut-être aider beaucoup.

+0

Excellent. Il me semble que je peux simplement utiliser Initialized sans se soucier de savoir si c'est un tableau, struct, ou un type simple, autre que pointeur, si je fournis simplement les opérateurs nécessaires pour tout ce qui précède. J'utiliserais le modèle with-inheritance pour la sémantique d'accès naturel des membres, mais je veux aussi prendre en charge des types simples, dont on ne peut pas hériter. – Mordachai

+0

La question principale qui me reste est de savoir comment utiliser une valeur intégrale (peut-être une énumération) pour l'aspect de spécialisation, au lieu d'une booléenne. Par conséquent, si je pouvais Initialiser , alors je serais peut-être dorée. Par exemple. Je ne voudrais pas nécessairement fournir des opérateurs d'index de tableau sauf quand le type est un tableau. Encore une fois, ceci est pour le code hérité, comme un wrapper de dépôt, plutôt que pour le nouveau, meilleur code qui utiliserait des vecteurs ou quelque chose de toute façon. – Mordachai

+0

Vous pouvez utiliser n'importe quel type entier en plus de bool. Mais vous voulez probablement juste une spécialisation pour les types intégrés et l'autre pour tout ce qui peut être hérité. – UncleBens

0

Je sais que cela ne répond pas à votre question, mais je pensais que les structures de POD étaient toujours zéro initialisées de toute façon.

+0

L'initialisation par défaut des POD se traduira par une initialisation zéro, mais cela n'arrive pas dans tous les cas. –

+0

Oh d'accord, c'est juste. – James

+0

Traduire en - ne s'en est pas rendu compte. Mais cela signifie toujours qu'il doit s'agir d'une variable membre pour que le compilateur génère ce constructeur par défaut (zéro). Si j'ai un RECT, et delcare un RECT r; dans un corps de fonction, il n'est PAS initialisé. Donc, il peut être pratique et aider à rendre mon code plus robuste pour utiliser un r Initialisé; ce qui est zéro pour moi. – Mordachai

0

Depuis que je suis en mesure d'utiliser les réponses de UncleBen pour créer une solution complète (aussi bon que je pense qu'il obtient à ce stade en C++), je voulais partager, au-dessous:

Ne hésitez pas à utiliser, mais je fais aucune garantie quant à son honorabilité pour tout usage que ce soit, etc., etc., être un adulte et assumer la responsabilité de vos propres actions fichues, bla, bla:

////////////////////////////////////////////////////////////// 
// 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(); 
}