Efficient passing of string literals in C++
在编写用于包装Lua的代码时,我遇到了传递字符串文字的需求,并开始想知道哪种方式最有效。
我可以在两个功能之间进行选择:
void lua_pushstring (lua_State* L, const char* str);
void lua_pushlstring(lua_State* L, const char* str, size_t len);
当然,第一个函数在内部使用strlen(),因此第二个函数更快。
现在,如果在编译时就知道了,我想避免计算字符串长度,如此处和此处所示:
1 2 3 4 5 6
| // Overload 1
template <size_t N>
inline void pushstring(lua_State* L, const char (&str) [N])
{
lua_pushlstring(L, str, N-1);
} |
使用字符串文字调用此函数时效果很好:pushstring(L,"test");当然,使用const char*调用时不会编译,例如在.cpp文件中的较长函数中:<铅>
1 2 3 4 5 6 7
| // this is in a .cpp file
void long_function(lua_State* L, const char* str)
{
// do lots of stuff
pushstring(L, str); // compile error
// do more stuff
} |
现在,如果我添加
1 2 3 4 5
| // Overload 2
inline void pushstring(lua_State* L, const char* str)
{
lua_pushstring(L, str);
} |
由于某种原因(C重载解析很棘手)优于Overload 1,因此从未调用过。
有解决这个问题的聪明方法吗?
- 我尝试了一些类似于您的尝试:ideone.com/Tb4mq也许这可以给您带来启发。
-
@没人:那应该是答案:)
如果同时声明了两个和第二个,则转发到第一个:
1 2 3 4
| void lua_pushlstring(lua_State* L, const char* str, size_t len);
inline void lua_pushstring (lua_State* L, const char* str)
{ lua_pushlstring(L, str, strlen(str)); } |
然后,当您使用文字(例如)调用第二个函数时,一个体面的编译器将优化strlen调用。它将内联
1
| lua_pushstring(L,"hello"); |
,并且因为文字上的strlen可以优化为常量,所以它将用以下调用代替:
1
| lua_pushlstring(L,"hello", 5); |
这为您提供了调用两个参数的形式的简单语法,而无需支付字面量的strlen。
已知长度时可以通过:
1
| lua_pushlstring(L, s.c_str(), s.length()); |
或者这也可以,但是不需要调用strlen
1
| lua_pushstring(L, s.c_str()); |
- 回复:"在字面量上可以优化为常量" –真的吗?我从未听说过。您是否可以提供一个可以证实这一点的资源,甚至可以列出支持该优化的编译器列表以及在什么情况下?
-
至少是GCC和Clang做到了,有关证明它的程序集输出,请参见Preview.tinyurl.com/cy3jnnt(查找movl\t$5, %edx)。如果其他编译器不支持,则停止使用它们。
-
综上所述,K.I.S.S。并让编译器完成工作。
-
真好我一直以为这是一个不错的优化,但不知道它实际上是在执行。
我将提供两种选择:
1 2 3 4
| void f( const char*, int );
template <int N> void f( const char (&str)[N] ) {
f( str, N-1 );
} |
(或更确切地说是std::size_t),现在具有字符串文字的用户可以调用第二个,而第二个将在内部调度到第一个。没有文字而是const char*的用户负责提供正确的大小。
-
是的,我也想到了这一点,但这很笨拙,不是吗?
-
@ marton78:这些是您唯一的选择。 void f(const char (&str)[N])仅在使用字符串常量或已知数组作为参数的情况下选择,而从不传递仅称为const char *的内容时(如在long_function中看到的那样)。
-
@ marton78:我过去曾经用过,但我觉得它并不笨拙。基本上,它是(ptr,size)函数和一个帮助程序,该帮助程序允许用户代码在字符串为文字的情况下不计算大小-同时在编译时获取潜在的错误。
-
最后,我结合使用了您和Nobody的答案:我没有人的模板调用您的void f(const char*, size_t);
-
@ marton78:在性能关键的代码中(大多数代码不是,这可能不会影响您),了解操作成本很重要。在这种情况下,提供这三种风味会带来负面影响,即用户代码不知道是否调用了strlen。考虑到调用是f( str.c_str() ),将拾取const char*的模板化重载,并且在用户代码已经知道大小为str.size()时,它将调用strlen。多余的糖衣会对您的程序产生负面影响。我宁愿让用户知道是否可以进行昂贵的操作。
-
...也就是说,您的抽象会影响性能。另一种方法是让用户在需要时调用strlen,或者在已知大小的情况下提供大小(如上例所示)
-
好吧,我明白你的意思,我同意。在性能上至关重要的代码f(const char*)应该被禁止。顺便说一句,"第一"和"第二"混在您的答案中。我会按照您的建议进行操作,但是,没人会回答有关过载解决的部分,因此,如果可以,我将保留他的回答。
-
@ marton78:感谢您收拾第一/第二件事。提供模板的实现时,我调换了顺序,却错过了更新文本的时间。关于滴答声,我不再需要其他代表了:)
要进一步详细说明您的模板版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream>
template <typename T>
inline void pushstring(T str);
template <int N>
inline void pushstring(const char (&str) [N])
{
std::cout << N << std::endl;
}
template <>
inline void pushstring(const char *str)
{
std::cout << str << std::endl;
} |
在此处查看测试运行:
参数错误->链接器错误:http://ideone.com/vZbj6
Rigt参数->运行良好:):http://ideone.com/iJBAo
-
多田真好只需将其模板化即可。知道为什么在这种情况下重载规则不同吗?
-
我猜模板专业化仅在未找到重载版本后才按需触发。由于直接过载匹配,因此不使用模板。但这只是一个猜测(如前所述)。
-
@ marton78:模板不允许在类型参数上进行任何转换,这意味着一旦重载确定适当的重载是基本模板,它就会推导T作为参数的类型。那就是过载解析结束并且模板实例化开始的地方。此时,T是const char*或const char [N],但是不允许进行转换,并且实例化了适当的专业化(非重载)
-
但是,如果其他代码依赖于对const char *的隐式转换,例如通过运算符T?我无法在尝试不同的变体的实际代码库中使用它。额外的问题,也许是相关的:如果std :: string作为参数还有另一个重载怎么办?