2014-09-09 12 views
18

Supponiamo che ho bisogno di chiamare una funzione foo che prende un const std::string riferimento da un gran numero di posti nel mio codice:Macro per std :: statico oggetto stringa da letterale

int foo(const std::string&); 
.. 
foo("bar"); 
.. 
foo("baz"); 

chiamare una funzione con una stringa letterale come questo creerà oggetti temporanei std::string, copiando ogni volta il letterale.

A meno che non mi sbagli, i compilatori non ottimizzeranno questo creando un oggetto statico std::string che può essere riutilizzato per le chiamate successive. So che g ++ ha meccanismi avanzati di pool di stringhe, ma non penso che si estenda agli stessi oggetti std::string.

posso fare questo "ottimizzazione" me stesso, il che rende il codice un po 'meno leggibile:

static std::string bar_string("bar"); 
foo(bar_string); 
.. 
static std::string baz_string("baz"); 
foo(baz_string); 

Utilizzando Callgrind, posso confermare che questo effettivamente accelerare il mio programma.

Ho pensato di provare a creare una macro per questo, ma non so se è possibile. Quello che vorrei è qualcosa di simile:

foo(STATIC_STRING("bar")); 
.. 
foo(STATIC_STRING("baz")); 

Ho cercato di creare un modello con la letterale come un parametro di template, ma che si è rivelato impossibile. E poiché una definizione di funzione in un blocco di codice non è possibile, sono completamente privo di idee.

C'è un modo elegante per farlo, o dovrò ricorrere alla soluzione meno leggibile?

+3

Utilizzare char * invece? – ZivS

+0

Quindi, per quanto riguarda '[]() -> std :: string const & {std :: string const s (" bar "); ritorna; }() '? – dyp

+0

con C++ 14, è possibile utilizzare "bar" letterali definiti dall'utente 's, ma non sono sicuro che faccia una copia o meno –

risposta

6

Si può usare qualcosa di simile per creare il tuo static std::string"sul posto":

#include <cstdint> 
#include <string> 

// Sequence of char 
template <char...Cs> struct char_sequence 
{ 
    template <char C> using push_back = char_sequence<Cs..., C>; 
}; 

// Remove all chars from char_sequence from '\0' 
template <typename, char...> struct strip_sequence; 

template <char...Cs> 
struct strip_sequence<char_sequence<>, Cs...> 
{ 
    using type = char_sequence<Cs...>; 
}; 

template <char...Cs, char...Cs2> 
struct strip_sequence<char_sequence<'\0', Cs...>, Cs2...> 
{ 
    using type = char_sequence<Cs2...>; 
}; 

template <char...Cs, char C, char...Cs2> 
struct strip_sequence<char_sequence<C, Cs...>, Cs2...> 
{ 
    using type = typename strip_sequence<char_sequence<Cs...>, Cs2..., C>::type; 
}; 

// struct to create a std::string 
template <typename chars> struct static_string; 

template <char...Cs> 
struct static_string<char_sequence<Cs...>> 
{ 
    static const std::string str; 
}; 

template <char...Cs> 
const 
std::string static_string<char_sequence<Cs...>>::str = {Cs...}; 

// helper to get the i_th character (`\0` for out of bound) 
template <std::size_t I, std::size_t N> 
constexpr char at(const char (&a)[N]) { return I < N ? a[I] : '\0'; } 

// helper to check if the c-string will not be truncated 
template <std::size_t max_size, std::size_t N> 
constexpr bool check_size(const char (&)[N]) 
{ 
    static_assert(N <= max_size, "string too long"); 
    return N <= max_size; 
} 

// Helper macros to build char_sequence from c-string 
#define PUSH_BACK_8(S, I) \ 
    ::push_back<at<(I) + 0>(S)>::push_back<at<(I) + 1>(S)> \ 
    ::push_back<at<(I) + 2>(S)>::push_back<at<(I) + 3>(S)> \ 
    ::push_back<at<(I) + 4>(S)>::push_back<at<(I) + 5>(S)> \ 
    ::push_back<at<(I) + 6>(S)>::push_back<at<(I) + 7>(S)> 

#define PUSH_BACK_32(S, I) \ 
     PUSH_BACK_8(S, (I) + 0) PUSH_BACK_8(S, (I) + 8) \ 
     PUSH_BACK_8(S, (I) + 16) PUSH_BACK_8(S, (I) + 24) 

#define PUSH_BACK_128(S, I) \ 
    PUSH_BACK_32(S, (I) + 0) PUSH_BACK_32(S, (I) + 32) \ 
    PUSH_BACK_32(S, (I) + 64) PUSH_BACK_32(S, (I) + 96) 

// Macro to create char_sequence from c-string (limited to 128 chars) without leading '\0' 
#define MAKE_CHAR_SEQUENCE(S) \ 
    strip_sequence<char_sequence<> \ 
    PUSH_BACK_128(S, 0) \ 
    ::push_back<check_size<128>(S) ? '\0' : '\0'> \ 
    >::type 

// Macro to return an static std::string 
#define STATIC_STRING(S) static_string<MAKE_CHAR_SEQUENCE(S)>::str 

Live example

+1

Incredibile! L'unico svantaggio è che utilizza una sintassi C++ 11 che non è disponibile in g ++ 4.4. Con 4.8 funziona perfettamente, però. – Masseman

10

Se la funzione foo non esegue una copia della stringa, la sua interfaccia non è ottimale. È meglio cambiarlo per accettare char const* o string_view, in modo che il chiamante non sia obbligato a costruire std::string.

o aggiungere sovraccarichi:

void foo(char const* str, size_t str_len); // Does real work. 

inline void foo(std::string const& s) { foo(s.data(), s.size()); } 
inline void foo(char const* s) { foo(s, strlen(s)); } 
+0

Se' foo' fa una copia della stringa, l'interfaccia è sotto ottimale. Se 'pippo' crea una copia della stringa, è meglio passare per valore. –

+1

Sono d'accordo. L'interfaccia non dovrebbe richiedere inutilmente 'std :: string', ma a volte l'interfaccia non è tua da cambiare, o l'oggetto stringa è effettivamente necessario più avanti sulla riga in' foo'. – Masseman

+0

@LuchianGrigore True. Per il riferimento gcc contato 'std :: string' accettandolo per riferimento a const vale quanto per valore in termini di copie di dati. –

1

Si potrebbe utilizzare Boost.Flyweight per fare un key-value flyweightconst char*-std::string. Non sono sicuro dei dettagli, potrebbe essere che sia sufficiente usare flyweight<std::string> ovunque.

1

Questo lavoro per semplici stringhe - w/o spazi bianchi:

#define DECL_STR(s) const std::string str_##s (#s) 

Utilizzo nell'intestazione (parse una volta!):

DECL_STR(Foo); 
DECL_STR(Bar); 

in codice:

func(str_Foo); 
func(str_Bar); 
+1

Bene, questo non è più leggibile o compatto della mia soluzione "meno leggibile". – Masseman

2

Se è possibile utilizzare spinta 1.55 o maggiore che puoi fare

#include <boost/utility/string_ref.hpp> 

void foo(const boost::string_ref& xyz) 
{ 
}