c++ - Macro para static std:: string object from literal
macros c-preprocessor (5)
Supongamos que necesito llamar a una función foo
que toma una referencia const std::string
de un gran número de lugares en mi código:
int foo(const std::string&);
..
foo("bar");
..
foo("baz");
Al llamar a una función con un literal de cadena como esta se crearán objetos std::string
temporales, copiando el literal cada vez.
A menos que me equivoque, los compiladores no optimizarán esto al crear un objeto std::string
estático por literal que puede reutilizarse para llamadas posteriores. Sé que g ++ tiene mecanismos avanzados de agrupación de cadenas, pero no creo que se extienda a los propios objetos std::string
.
Puedo hacer esta "optimización" yo mismo, lo que hace que el código sea un poco menos legible:
static std::string bar_string("bar");
foo(bar_string);
..
static std::string baz_string("baz");
foo(baz_string);
Usando Callgrind, puedo confirmar que esto efectivamente acelera mi programa.
Pensé que intentaría hacer una macro para esto, pero no sé si es posible. Lo que querría es algo como:
foo(STATIC_STRING("bar"));
..
foo(STATIC_STRING("baz"));
Intenté crear una plantilla con el literal como parámetro de plantilla, pero eso resultó imposible. Y como la definición de una función en un bloque de código no es posible, no tengo más ideas.
¿Hay una forma elegante de hacerlo, o tendré que recurrir a una solución menos legible?
Esto funcionará para cadenas simples, sin espacios en blanco:
#define DECL_STR(s) const std::string str_##s (#s)
Uso en el encabezado (parse una vez!):
DECL_STR(Foo);
DECL_STR(Bar);
En codigo:
func(str_Foo);
func(str_Bar);
Podría usar Boost.Flyweight para hacer un peso Boost.Flyweight de valor clave de const char*
a std::string
. No estoy seguro de los detalles, puede ser que sea suficiente usar flyweight<std::string>
todas partes.
Puedes usar algo como para crear tu static std::string
"en lugar" :
#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
Si esa función foo
no hace una copia de la cadena, entonces su interfaz es subóptima. Es mejor cambiarlo para que acepte char const*
o string_view
, para que el llamante no tenga que construir std::string
.
O añadir sobrecargas:
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)); }
Si puedes usar boost 1.55 o mayor puedes hacerlo
#include <boost/utility/string_ref.hpp>
void foo(const boost::string_ref& xyz)
{
}