2016-09-09 3 views
1

J'ai un tas de code, qui est encombré avec des blocs de préprocesseur, comme #ifdef FEATURE_A, #ifdef _MSC_VER et ainsi de suite.Remplacer les blocs #ifdef préprocesseur par l'implémentation de modèle

Je voudrais refactoriser une partie du code afin de remplacer certains blocs de préprocesseur par des implémentations de modèles.

EDIT: La tâche est de ne pas supprimer tous les blocs de préprocesseur, mais certains d'entre eux, afin de se débarrasser de l'encombrement.

Je ne veux pas vous ennuyer avec des exemples de foobar, voici donc un du monde réel (pas mon code):

template <typename T> 
std::string demangle() 
{ 
#ifdef __GNUC__ 
    size_t sz; 
    int status; 
    char* ptr = abi::__cxa_demangle(typeid(T).name(), 0, &sz, &status); 
    std::string name(ptr ? ptr : "", ptr ? strlen(ptr) : 0); 
    if(ptr){ free(ptr); } 
    std::string::size_type pos = name.rfind("::"); 
    if(pos != std::string::npos) 
    { 
    name = name.substr(pos + 2); 
    } 
#elif _MSC_VER 
    std::string name(typeid(T).name()); 
    static const std::string struct_prefix("struct "); 
    static const std::string class_prefix("class "); 
    static const std::string ptr_postfix(" *"); 

    std::string::size_type 
    at = name.find(struct_prefix); 
    if(at != std::string::npos) { name.erase(at, struct_prefix.size()); } 
    at = name.find(class_prefix); 
    if(at != std::string::npos) { name.erase(at, class_prefix.size()); } 
    at = name.find(ptr_postfix); 
    if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); } 
#else 
    std::string name(typeid(T).name()); 
#endif 
    return name; 
} 

Question 1: Comment transformer ce à une mise en œuvre du modèle de equvalent ?

Question 2: Pourquoi vaut-il la peine, ou pourquoi pas?

+0

De toute façon, vous avez besoin de ces définitions de préprocesseur. Peut-être que vous avez trois fonctions 'std :: string demangle()' (peut-être réparties sur plusieurs fichiers pour plusieurs compilateurs). –

+0

Oui, vous en avez besoin de toute façon, mais vous pouvez supprimer le fouillis de l'implémentation. – mrAtari

+0

En supposant que vous puissiez remplacer les directives du préprocesseur par des modèles (d'autres ne le peuvent pas), il est peu probable qu'un tel remplacement utilise moins de volume total de texte. Donc, vous obtiendriez quelque chose de plus gros et de plus maladroit, qui fonctionnerait d'une manière surprenante pour le lecteur. Après tout, les codeurs C++ * savent * quelles sont les directives PP. Donc, je ne vois pas comment cela pourrait être une victoire. –

Répondre

1

MISE À JOUR: Comme d'autres l'ont souligné, et l'exemple du monde réel pour autant qu'il présente dans une belle manière, il y a des cas où il est impossible de se débarrasser de préprocesseur. Surtout quand PP couvre la plate-forme specifica comme abi::__cxa_demangle. Cependant, je suis novice dans le domaine de la programmation de méta-modèles. Il est donc intéressant de connaître les avantages et les inconvénients de cette approche.

Voici ma propre solution, malheureusement presque 3 fois plus longue que le code original. C'est principalement à cause de certains trucs d'aide nécessaires pour la solution.

Pour la question 1:

J'ai utilisé un ENUM et Int2Type mappeur à Tranlate les valeurs de préprocesseur dans les types définis par l'utilisateur. Dans la deuxième étape, les différentes parties sont extraites vers des modèles partiellement spécialisés.

est ici l'exemple de code refondus (version 2):

#include <iostream> 
#include <cstring> 
#include <stdlib.h> 
#include <typeinfo> 


enum CompilerIds 
{ 
    ANY = 0, 
    MSC = 1, 
    GNUC = 2 
}; 

template <int v> 
struct Int2Type 
{ 
    const static int value= v; 
}; 

#ifdef __GNUC__ 
#include <cxxabi.h> 
typedef Int2Type<GNUC> CompilerType; 
#elif _MSC_VER 
namespace abi 
{ 
    char* __cxa_demangle(const char* name, int n, size_t* sz, int* status); 
} 
typedef Int2Type<MSC> CompilerType; 
#else 
typedef Int2Type<ANY> CompilerType; 
#endif 

template <int N> 
struct compiler_traits 
{ 
    static std::string demangle(std::string name) 
    { 

     return name; 
    } 
}; 

template <> 
struct compiler_traits<GNUC> 
{ 
    static std::string demangle(std::string name) 
    { 
     size_t sz; 
     int status; 
     char* ptr = abi::__cxa_demangle(name.c_str(), 0, &sz, &status); 
     std::string retName(ptr ? ptr : "", ptr ? strlen(ptr) : 0); 
     if(ptr){ free(ptr); } 
     std::string::size_type pos = retName.rfind("::"); 
     if(pos != std::string::npos) 
     { 
      retName = retName.substr(pos + 2); 
     } 
     return retName; 
    } 
}; 

template <> 
struct compiler_traits<MSC > 
{ 
    static std::string demangle(std::string name) 
    { 
     static const std::string struct_prefix("struct "); 
     static const std::string class_prefix("class "); 
     static const std::string ptr_postfix(" *"); 

     std::string::size_type 
     at = name.find(struct_prefix); 
     if(at != std::string::npos) { name.erase(at, struct_prefix.size()); } 
     at = name.find(class_prefix); 
     if(at != std::string::npos) { name.erase(at, class_prefix.size()); } 
     at = name.find(ptr_postfix); 
     if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); } 

     return name; 
    } 
}; 

template <typename T, typename traits = compiler_traits<CompilerType::value> > 
struct demangle 
{ 
    static std::string Exec() 
    { 
     return traits::demangle(typeid(T).name()); 
    } 
}; 


int main() 
{ 
    std::cout << "\n mangled:" << typeid(Int2Type<GNUC>).name(); 
    std::cout << "\n demangled:" << demangle<Int2Type<GNUC> >::Exec() <<"\n"; 

    std::cout << "\n instatiate the msc version:" << compiler_traits<MSC>::demangle("struct Int2Type<2>") <<"\n"; 

    return 0; 
} 

La sortie pour gcc 4.9.2 est:

mangled:8Int2TypeILi2EE 
demangled:Int2Type<2> 

instatiate the msc version:Int2Type<2> 

je devais pirater le abi::__cxa_demangle avec une déclaration en avant pour le MS compilateur (ne le ferait jamais dans le code de production). Vous verrez, que l'instanciation de compiler_traits<GNUC> sur un système MS échouera.

Pour la question 2:

Il est possible de remplacer les blocs PP avec des modèles en principe, mais peut aussi avoir des inconvénients graves. Je ai toujours tendance à lui donner une chance, que cela vaut la peine, surtout quand vous n'avez pas "truc de la plate-forme", mais la fonctionnalité de commutation comme #ifdef FEATURE_A et ainsi de suite.

Avec des modèles:

  • Extension est très facile et ne cassera pas OCP
  • Il améliore readabilty, surtout si vous avez beaucoup #
  • Code injoignable
  • Otherways de ifdef devient testable
2

Ce n'est pas possible. Les modèles C++ utilisent une recherche en deux phases (Two phase name lookup for C++ templates - Why?). Par conséquent, tous les noms utilisés dans un modèle de fonction qui ne dépendent pas d'un paramètre de modèle doivent être disponibles au moment de la déclaration. Votre implémentation GCC utilise le nom abi::__cxa_demangle. Par conséquent, toute implémentation ne fournissant pas ce nom doit rejeter votre code (d'autres non, mais uniquement parce qu'ils n'implémentent pas correctement la recherche en deux phases: What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?). La seule façon de contourner ce problème est de contenir l'utilisation de abi::__cxa_demangle dans un bloc de préprocesseur #ifdef, ce qui implique effectivement l'utilisation de votre implémentation d'origine de toute façon.

+0

très bon point! – mrAtari