2016-06-19 16 views
8

Come posso semplificare questo codice?Come posso semplificare questa "variabile come parametro template" in C++?

mfer::i_value* make_empty_value(mfer::tag tag_) 
{ 
    if (tag_ == mfer::tag::mwf_ble) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_ble>()); 
    } else if (tag_ == mfer::tag::mwf_chn) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_chn>()); 
    } else if (tag_ == mfer::tag::mwf_blk) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_blk>()); 
    } else if (tag_ == mfer::tag::mwf_seq) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_seq>()); 
    } else if (tag_ == mfer::tag::mwf_man) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_man>()); 
    } else if (tag_ == mfer::tag::mwf_ivl) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_ivl>()); 
    } else if (tag_ == mfer::tag::mwf_sen) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_sen>()); 
    } else if (tag_ == mfer::tag::mwf_wfm) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_wfm>()); 
    } else if (tag_ == mfer::tag::mwf_pre) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pre>()); 
    } else if (tag_ == mfer::tag::mwf_off) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_off>()); 
    } else if (tag_ == mfer::tag::mwf_nul) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_nul>()); 
    } else if (tag_ == mfer::tag::mwf_pnt) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pnt>()); 
    } else if (tag_ == mfer::tag::mwf_nte) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_nte>()); 
    } else if (tag_ == mfer::tag::mwf_txc) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_txc>()); 
    } else if (tag_ == mfer::tag::mwf_flt) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_flt>()); 
    } else if (tag_ == mfer::tag::mwf_skw) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_skw>()); 
    } else if (tag_ == mfer::tag::mwf_mss) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_mss>()); 
    } else if (tag_ == mfer::tag::mwf_pnm) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pnm>()); 
    } else if (tag_ == mfer::tag::mwf_pid) { 
     return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pid>()); 
    } 

    return nullptr; 
} 

breve affermando,

  • mfer :: tag è l'enumerazione, definita come enum tag {}; nel namespace mfer.

  • mfer :: i_value è una classe astratta.

    class i_value {}; 
    
  • mfer :: t_value è di classe su modelli come,

    template <mfer::tag tag_type> 
    class t_value : public i_value {}; 
    

In questo momento, non so come semplificare this make_empty_value().

Idealmente, voglio fare in questo modo:

mfer::i_value* make_empty_value(mfer::tag tag_) 
{ 
    return memory_manager::instance().add(new mfer::t_value<tag_>()); 
} 

Ma so che è modello, così sopra uno non ha senso.

C'è qualche idea di semplificare questo codice? (Alcune funzioni C++ moderne, librerie Boost e così via)

+2

Si prega di non pubblicare il tuo codice come un'immagine. Pubblicalo come il testo nella risposta. – milleniumbug

+0

È possibile cambiare questi 'se's a' interruttore/caso' se 'mfer :: tag :: mwf_ *' sono costanti. Questo potrebbe migliorare la leggibilità. – ForceBru

+1

Puoi condividere la definizione della classe 't_value'? Almeno le parti rilevanti in cui 'tag_type' influenza la definizione di questa classe. È abbastanza possibile, in base a ciò che si sta facendo, che 't_value' non debba essere necessariamente una classe template. tag_type potrebbe essere passato al costruttore. – selbie

risposta

2

Con un po 'di lavoro modello, si può ottenere la funzione di fabbrica fino a:

i_value* make_empty_value(tag tag_type) 
{ 
    static constexpr auto factory = make_factory(all_tags()); 

    auto index = std::size_t(tag_type - tag::first); 
    if (index < tag::ntags) { 
     return memory_manager::instance().add(factory[index]()); 
    } 
    else { 
     return nullptr; 
    } 
} 

codice qui sotto.

La mappa del generatore i_value viene creata in fase di compilazione, consentendo una ricerca costante.

vincoli:

  • i valori nella enumerazione devono essere consecutivi, ma non hanno bisogno di cominciare a zero.

  • questa demo richiede C++ 14. Può essere facilmente adattato per funzionare con C++ 11. Per C++ 03 vorremmo raggiungere per aumentare mpl o boost_pp.

completo esempio di lavoro:

#include <array> 
#include <utility> 
#include <deque> 
#include <iostream> 

// minimal implementation of virtual base 
class i_value { 
public: 
    virtual void prove() const = 0; 
    virtual ~i_value() = default; 
}; 

// tag enum - note that we have supplied some extra introspection information 
// these could just as well be constexpr integers outside the enum 
enum tag 
{ 
    ble, 
    chn, 
    blk, 
    seq, 

    first = ble,    // first available tag 
    last = seq,    // last available tag 
    ntags = last-first  // number of tags 
}; 

/// Function to offset an index sequence by the distance from 
/// zero to the first available tag - in case the first tag is not zero 
template<std::size_t...tags> 
constexpr auto tag_offset(std::index_sequence<tags...>) 
{ 
    return std::index_sequence<(tags + tag::first)...>(); 
} 

/// Function to compute an index sequence of all valid tags 
constexpr auto all_tags() 
{ 
    return tag_offset(std::make_index_sequence<std::size_t(ntags)>()); 
} 

/// Factory function to generate a derived class for a given tag 
template <tag tag_type> 
class t_value : public i_value { 
    void prove() const override { void(std::cout << "I have tag " << tag_type << std::endl); } 
    ~t_value() { void(std::cout << "tag " << tag_type << " destroyed" << std::endl); } 
}; 

template<tag tag_type> 
i_value* make_instance() 
{ 
    return new t_value<tag_type>(); 
} 


/// Function to generate a 'factory' - an array of factory functions, one for 
/// each tag in the variadic template argument tags... 
/// Note that the array is zero-based, the tags may not be. All we care about 
/// here is the size of the list of tags (and their values) 
/// 
template<std::size_t...tags> 
constexpr auto make_factory(std::index_sequence<tags...>) 
{ 
    return std::array<i_value* (*)(), sizeof...(tags)> 
    { 
     &make_instance<static_cast<tag>(tags)>... 
    }; 
} 

// minimal memory manager 
struct memory_manager { 
    struct impl { 
     i_value* add(i_value* item) { 
      _ivalues.push_back(item); 
      return item; 
     }; 
     ~impl() { 
      for (auto i = _ivalues.rbegin() ; i != _ivalues.rend() ; ++i) { 
       delete *i; 
      } 
     } 
     std::deque<i_value*> _ivalues; 
    }; 
    static impl& instance() 
    { 
     static impl _instance = {}; 
     return _instance; 
    } 
}; 

// here is resulting factory function. 
i_value* make_empty_value(tag tag_type) 
{ 
    static constexpr auto factory = make_factory(all_tags()); 

    auto index = std::size_t(tag_type - tag::first); 
    if (index < tag::ntags) { 
     return memory_manager::instance().add(factory[index]()); 
    } 
    else { 
     return nullptr; 
    } 
} 

// test 
int main() 
{ 
    for(auto tag_type : { tag::ble, tag::chn }) 
    { 
     auto pvalue = make_empty_value(tag_type); 
     pvalue->prove(); 
    } 
} 

risultato atteso:

I have tag 0 
I have tag 1 
tag 1 destroyed 
tag 0 destroyed 
+0

Posso chiedere perché hai svuotato void() alle funzioni di t_value? Ne fa uno migliore? – Jafffy

+0

Che ci crediate o no, il wrapping delle operazioni ostream nel vuoto impedisce a xcode di generare erroneamente il codice. Capita anche di essere un modo portatile per indicare che stai deliberatamente ignorando il valore di ritorno. –

0

Non si utilizza direttamente l'esempio, ma si può fare qualcosa sulle linee sottostanti, cioè convertire enum in un tipo.

enum Type { 
    Type_A, 
    Type_B, 
}; 

template <Type T> 
struct Enum2Type { 
    constexpr static const Type type = T; 
}; 

template <typename T> 
mfer::i_value* make_empty_value(T tag_type) 
{ 
    return memory_manager::instance().add(new mfer::t_value<tag_type.type>()); 
} 

auto val = make_empty_value(Enum2Type<Type_A>()); 
auto val2 = make_empty_value(Enum2Type<Type_B>()); 
+0

Ma se uso questo codice, non posso passare la variabile attraverso questa funzione, vero? – Jafffy

+0

Se si intende fare argomento di variabile 'make_empty_value', allora no, non è possibile. Il modello ha bisogno di un certo valore per poter vedere un tempo di compilazione. Ad un certo punto dovrai utilizzare una casella di scelta se è un parametro di runtime. – Arunmu

+0

Capisco. (Mi aspettavo che la situazione dovesse essere implementata in quel modo sarà più normale, quindi ho creduto che ci fosse un pattern per quei codici. – Jafffy

1

È possibile associare le etichette a un metodo di fabbrica;

typedef std::unordered_map<mfer::tag,std::function<mfer::i_value*()>> TagMap; 

TagMap create_tag_map() 
{ 
    TagMap map; 

    map[mfer::tag::mwf_ble] = [](){ return new mfer::t_value<mfer::tag::mwf_ble>(); }; 
    map[mfer::tag::mwf_chn] = [](){ return new mfer::t_value<mfer::tag::mwf_chn>(); }; 
    map[mfer::tag::mwf_blk] = [](){ return new mfer::t_value<mfer::tag::mwf_blk>(); }; 
    //... 

    return map; 
} 

Il metodo create_empty_value potrebbe quindi simile a questa:

mfer::i_value* make_empty_value(mfer::tag tag_) 
{ 
    static TagMap factory = create_tag_map(); 

    auto it = factory.find(tag_);  
    if(it != factory.end()) 
    { 
     return memory_manager::instance().add(it->second()); 
    } 

    return nullptr; 
} 

vedere versione semplificata Live on Coliru

+1

Ma se lo usa solo una volta, sta scrivendo anche * più * codice per creare il modello di fabbrica per un uso una tantum. – CoffeeandCode

1

È possibile creare una funzione template ricorsiva se il valore enumerate segue un modello noto (per impostazione predefinita, il valore enumerato corrisponde al precedente enumerazione +1):

Se non si conosce la regola costitutiva del proprio enumerato, non è possibile farlo (la legge costitutiva generale è come in questo esempio, x [i + 1] = x [i] +1 o x [i + 1] = x [i] < < 1 (spostamento a sinistra). Altrimenti non è possibile iterare su elementi di un'enumerazione.

Nota: la funzione compute sarà certamente inline, ma nel dubbio è possibile utilizzare attributo specifico compilatore come __forceinline con MSVC o __attribute__((__always_inline__)) con GCC o clang.

0

L'unico scopo della semplificazione che vedo è la rimozione del codice boilerplate sostituendolo con una macro fissa. Questo sarà rilassante per lo spettatore.

Invece di tanti if-else if, ne fanno un switch/case come di seguito:

#define CASE(TAG) \ 
    case TAG: return memory_manager::instance().add(new mfer::t_value<TAG>()) 

mfer::i_value* make_empty_value(const mfer::tag tag_) 
{ 
    switch(tag_) { 
    { 
    CASE(mfer::tag::mwf_ble); 
    CASE(mfer::tag::mwf_chn); 
    CASE(mfer::tag::mwf_blk); 
    //... 
    default: break; 
    } 
    return nullptr; 
} 

#undef CASE 
Problemi correlati