来自文字的静态std::string对象的宏

Macro for static std::string object from literal

本文关键字:string 对象 静态 文字 std      更新时间:2023-10-16

假设我需要调用一个函数foo,该函数从代码中的许多位置获取常量std::string引用:

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

用这样的字符串文字调用函数将创建临时std::string对象,每次都复制文字。

除非我弄错了,否则编译器不会通过为每个文本创建一个可重复用于后续调用的静态std::string对象来优化这一点。我知道g++有高级的字符串池机制,但我不认为它能扩展到std::string对象本身。

我可以自己做这种"优化",这会使代码的可读性降低:

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

使用Callgrind,我可以确认这确实加快了我的程序。

我想我会尝试为它做一个宏,但我不知道这是否可能。我想要的是:

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

我尝试创建一个以文本作为模板参数的模板,但事实证明这是不可能的。由于在代码块中定义函数是不可能的,所以我完全没有想法。

有没有一种优雅的方法可以做到这一点,或者我将不得不求助于可读性较差的解决方案?

如果函数foo没有复制字符串,那么它的接口是次优的。最好将其更改为接受char const*string_view,这样调用者就不需要构造std::string

或者添加过载:

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)); }

您可以使用类似的东西来创建static std::string"就位"


#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 ''
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<'', 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 (`` 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] : ''; }
// 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 ''
#define MAKE_CHAR_SEQUENCE(S) 
    strip_sequence<char_sequence<> 
    PUSH_BACK_128(S, 0) 
    ::push_back<check_size<128>(S) ? '' : ''> 
    >::type
// Macro to return an static std::string
#define STATIC_STRING(S) static_string<MAKE_CHAR_SEQUENCE(S)>::str

实例

gcc有一个简化MAKE_CHAR_SEQUENCE:的扩展

template <typename CHAR, CHAR... cs>
constexpr auto operator ""_c() { return char_sequence<cs...>{}; }

如果你可以使用boost 1.55或更高版本,你可以进行

#include <boost/utility/string_ref.hpp>
void foo(const boost::string_ref& xyz)
{
}

您可以使用Boost.Fireweight生成从const char*std::string的键值蝇量级。我不确定细节,可能是到处使用flyweight<std::string>就足够了。

这将适用于简单的字符串-不带空格:

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

标头中的用法(解析一次!):

DECL_STR(Foo);
DECL_STR(Bar);

代码中:

func(str_Foo);
func(str_Bar);