【问题标题】:Efficient passing of string literals in C++在 C++ 中有效地传递字符串文字
【发布时间】:2012-06-07 14:21:23
【问题描述】:

在编写包装 Lua 的代码时,我遇到了传递字符串文字的需求,并开始想知道哪种方式最有效。

我有两种功能可供选择:

  1. void lua_pushstring (lua_State* L, const char* str);
  2. void lua_pushlstring(lua_State* L, const char* str, size_t len);

当然,第一个函数在内部使用strlen(),因此第二个更快。

现在,如果在编译时已知,我想避免计算字符串长度,如 herehere 所示:

// 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 文件中的较长函数中:

// 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
}

现在如果我添加

// Overload 2
inline void pushstring(lua_State* L, const char* str)
{
    lua_pushstring(L, str);
}

由于某种原因(C++ 重载解析很棘手),它优先于 Overload 1,因此永远不会被调用。

有没有聪明的方法来解决这个问题?

【问题讨论】:

  • 我尝试了类似你的东西:ideone.com/Tb4mq也许这会给你灵感。
  • @Nobody:这应该是答案:)

标签: c++ string templates


【解决方案1】:

如果您声明 both 并将第二个转发给第一个:

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 调用,例如它将内联

lua_pushstring(L, "hello");

并且因为 strlen 在文字上可以优化为常量,所以它将替换为调用:

lua_pushlstring(L, "hello", 5);

这为您提供了调用两个参数形式的简单语法,而无需为文字支付 strlen

当长度已知时可以传递:

lua_pushlstring(L, s.c_str(), s.length());

或者这也可以,但是不必要地调用strlen

lua_pushstring(L, s.c_str());

【讨论】:

  • 回复:“字面上的 strlen 可以优化为常量”——真的吗?我从来没有听说过。您能否提供一个来源来证实这一点,甚至可能提供支持此优化的编译器列表以及在哪些情况下?
  • 至少 GCC 和 Clang 可以做到这一点,请参阅 preview.tinyurl.com/cy3jnnt 以获取证明它的程序集输出(查找 movl $5, %edx)。如果其他编译器不支持它,请停止使用它们。
  • 总之,K.I.S.S.让编译器完成它的工作。
  • 不错。我一直认为这将是一个很好的优化,但不知道它实际上是执行的。
【解决方案2】:

我会提供两种选择:

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 (&amp;str)[N]) 只会选择字符串文字或已知数组作为参数,绝不会在传递仅称为 const char* 的东西时选择,如 long_function 所示。
  • @marton78:我以前用过这个,我不觉得它笨拙。它基本上是(ptr,size) 函数和一个助手,如果字符串是文字,它允许用户代码不计算大小——同时在编译时发现潜在的错误。
  • 最后,我结合了你和Nobody的答案:我有Nobody的模板调用你的void f(const char*, size_t);
  • @marton78:在性能关键代码中(大多数代码不是,这可能不会影响您)了解操作的成本 很重要。在这种情况下,提供三种风格会产生负面影响,即用户代码不知道是否调用了strlen。考虑到调用是f( str.c_str() )const char* 的模板化重载将被拾取,它会调用strlen,而用户代码已经知道大小是str.size()。额外的糖衣会对您的计划产生负面影响。我宁愿让用户知道是否可能进行昂贵的操作。
【解决方案3】:

进一步详细说明您的模板版本:

#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 是参数的类型。这就是重载决议结束和模板实例化开始的地方。此时Tconst char*const char [N],但不允许转换,并实例化适当的特化(不是重载
  • 但是如果其他代码依赖于隐式转换为 const char* 会怎样,例如通过运算符 T?我无法让它在尝试不同变体的真实代码库中工作。额外的问题,也许是相关的:如果有另一个以 std::string 作为参数的重载怎么办?
猜你喜欢
  • 1970-01-01
  • 2014-03-19
  • 1970-01-01
  • 2012-11-04
  • 2016-04-09
  • 2017-04-06
  • 2015-05-13
  • 2017-01-14
  • 1970-01-01
相关资源
最近更新 更多