2017-05-08 7 views
0

Je suis en train de réécrire du code existant - alors qu'auparavant, toutes les informations de réponse étaient stockées dans une matrice de chaînes en mémoire. Basé sur le type de données, les données ont été transformées à divers endroits. Voici une maquette rapide de la configuration que je vise. Essentiellement, vous avez quelques questions - et la structure des réponses stockées dans la base de données dépend du type de données. En général, j'évite de traiter avec void *, et de les convertir en un type approprié - mais je n'ai pas trouvé de meilleure solution pour exécuter un code générique (au moyen de lambdas), ou être spécifique si le type de données est connu. Les classes modélisées n'aideront pas dans ce cas, car toutes les réponses doivent être stockées dans le même vecteur (car certaines opérations arithmétiques sont appliquées à toutes les réponses basées sur des règles prédéfinies).Combinaison d'un code de typesafe avec des décisions d'exécution

Un conseil est apprécié.

#include <vector> 
#include <memory> 


struct AddressData 
{ 
    wchar_t Line1[50]; 
    wchar_t Line2[50]; 
    long CountrySeqNo; 

    AddressData() 
    { 
     memset(this, 0, sizeof(*this)); 
    }; 
}; 

struct GenericData 
{ 
    wchar_t value[200]; 

    GenericData() 
    { 
     memset(this, 0, sizeof(*this)); 
    }; 
}; 

enum class DataType 
    : short 
{ 
    GENERIC, 
    ADDRESS 
}; 

class AnswerBase 
{ 
protected: 
    const void* const data; 
    const DataType dataType; 

protected: 
    AnswerBase(const DataType datatype, const void* const _data) 
     : dataType(datatype), data(data) 
    { 
     if (data == nullptr) 
      throw std::exception("Data may not be initialized as NULL"); 
    }; 

public: 
    /* 
     Some generic methods here that would apply logic by means of lambdas etc - these would be overwritten in the derived classes 
    */ 

    template<typename T> const T& GetData() { static_assert(false, "The given type is not supported"); }; 

    template<> 
    const GenericData& GetData() 
    { 
     if (DataType::GENERIC != dataType) 
      throw std::exception("The requested type does not match the value that initialised data"); 

     return *static_cast<const GenericData* const>(data); 
    }; 
    template<> 
    const AddressData& GetData() 
    { 
     if (DataType::ADDRESS != dataType) 
      throw std::exception("The requested type does not match the value that initialised data"); 

     return *static_cast<const AddressData* const>(data); 
    }; 


}; 


class AddressAnswer 
    : public AnswerBase 
{ 
public: 
    AddressAnswer() 
     : AnswerBase(DataType::ADDRESS, &answer) 
    { 
    }; 

protected: 
    AddressData answer; 
}; 


class GenericAnswer 
    : public AnswerBase 
{ 
public: 
    GenericAnswer() 
     : AnswerBase(DataType::GENERIC, &answer) 
    { 
    }; 

protected: 
    GenericData answer; 
}; 


int main() 
{ 
    std::vector<std::shared_ptr<AnswerBase>> answers; 
    answers.push_back(std::make_shared<GenericAnswer>()); 
    answers.push_back(std::make_shared<AddressAnswer>()); 


    // In some parts of code - interact with generic methods without needing to check the underlying data type 
    // .... 
    // .... 

    // In parts of code where we know we are dealing with a given type - like saving to a DB 


    auto val1 = answers[0]->GetData<GenericData>().value; 
    auto val2 = answers[1]->GetData<AddressData>().Line1; 

    // this will give a runtime failure 
    //auto val3 = answers[0]->GetData<AddressData>().Line1; 


    return 0; 
} 
+0

Vous pouvez utiliser 'varaint' au lieu de' void * '. – Jarod42

+0

Je pensais à la variante - mais comme VC++ ne supporte pas encore le C++ 17 - je devrais inclure la bibliothèque boost dans mon projet (ce qui n'est pas un problème majeur). Le membre variant existerait-il toujours dans la classe de base? – Floris

Répondre

1

variant est la manière propre de faire ceci. Stockez-le dans le parent.

Alternativement, fournissez un variant<A,B> GetData() dans le parent. La visite est maintenant encapsulée dans la variante renvoyée. La parent stocke les données.

Alternativement, fournissez un virtual variant<A,B> GetData() = 0. Le type enfant renvoie les données, A ou B, dans la variante en question.

Vous pouvez également écrire virtual A* GetA() = 0; virtual B* GetB() = 0;. Alors peut-être écrire une méthode de modèle appelé GetData<T> tels que GetData<A>() appels GetA, etc.

Sinon, écrivez virtual A* Get(tag_t<A>) = 0; virtual B* Get(tag_t<B>)=0;, où

template<class T> 
struct tag_t { 
    using type=T; 
    constexpr tag_t(){} 
}; 
template<class T> 
constexpr tag_t<T> tag{}; 

est une étiquette utilisée pour l'expédition. Vous pouvez maintenant appeler la bonne interface virtuelle en faisant Get(tag<AddressData>).

Dans ces cas virtuels, les données sont stockées dans le type dérivé.

+0

Merci - cela semble mieux. @ Jarod42 et Yakk, j'apprécie l'entrée. Je vais essayer les deux suggestions et voir ce que je préfère à la fin – Floris