如何正确传递C字符串到拉姆达一个可变参数模板函数内部
这里是我的复杂局面:如何正确传递C字符串到拉姆达一个可变参数模板函数内部
我有一个函数使用可变参数模板和lambda:
template<typename...Args>
void foo(Args...args) {
// the arguments are passed to the lambda
_func = [args...](){ do_sth(args...); };
}
在观察特定的事件中, lambda _func
将被解雇。 我的问题是,一些我传递的参数是C字符串,它们可以指向临时STD字符串是这样的:
const char *pStr = __temp_std__string__.c_str();
鉴于我打电话foo(pStr);
,当_func
被调用时,临时字符串PSTR是指向,已被释放。我想知道是否存在通用的方法来处理这个问题。我正在使用C++ 11。
编辑:
也许我应该张贴我的整个故事,正如你们许多人建议通过的std :: string代替C字符串,有我无法逃避它的理由。
我正在开发使用cocos2d-x的游戏,它部署C++ 11。我想要做的是支持当玩家更改他们的语言偏好(从UI中选择)时自动定位标签。
我已经保存在几个文件的文本,和他们每个人都包含一个单一语言的本地化的文本,它们基本上是以下结构下:
{
"key1" : "_localized_text1_",
"key2" : "_localized_text2_",
...
}
的想法是观察事件关于语言偏好的改变(通过通知),我会得到一个表示该语言的关键字,以便从正确的文件中获取本地化的文本。这是我如何实现它的对象类Label
方式:
class Label {
// this method would update the label's displayed text
void setString(const std::string& text);
// set a callback for changing language
void setOnLanguageChangeFunc(std::function<void(Notification*)> func);
// set a localised text, which would be updated on changing language
void setLocalizeString(const std::string& key);
};
的核心功能是setLocalizeString
(我跳过了其他2种方法的实现,因为他们是从他们的声明不够直观):
void Label::setLocalizeString(const std::string& key) {
// the callback lambda
auto callback = [=](Notification *pNotification){
setString(LOCALIZED_STRING(key));
}
// assign the lambda
setOnLanguageChangeFunc(callback);
}
其中LOCALIZED_STRING
是用键获取本地化字符串的宏助手;并且lambda callback
将被保存为Label
的本地成员变量setOnLanguageChangeFunc
。
这在大多数情况下,伟大的,是什么让复杂的情况是,有参与本地化的文本格式说明,例如:
{
...
"keyN" : "%s eats %d cookies",
...
}
这种格式的占位符在代码动态传递:
// formatStr = "Tom eats 5 cookies"
std::string formatStr = StringKit::stringWithFormat("%s eats %d cookies", "Tom", 5);
其中StringKit
是格式化的字符串的实用程序,它接受这将被传递到可变参数3210以产生输出。现在你知道为什么我需要传递C字符串而不是std :: string,它只是由于格式化字符串的底层方法。
现在我要修改Label::setLocalizeString
,使其能够消化可能的可变参数:
template<typename... Args>
void setLocalizeString(const std::string& key, Args... args)
{
// the callback lambda
auto callback = [=](Notification *pNotification){
setString(StringKit::stringWithFormat(LOCALIZED_STRING(sKey), args...));
}
// assign the lambda
setOnLanguageChangeFunc(callback);
}
这是它的使用情况:
// on changing language, the label would display "Tom eats 5 cookies"
pLabel->setLocalizeString("keyN", "Tom", 5);
这种情况下,将工作就像一个魅力作为C字符串参数是全局的,但是当它从传递过来std :: string:
std::string tempStr = "Tom";
pLabel->setLocalizeString("keyN", tempStr.c_str(), 5);
C字符串“Tom”将失去调用lambda回调的值,因为指向的std :: string已经消失。
我已经尝试了几种方法,比如玩元组事物,或者在lambda中捕获基本类型的包装类,但是他们都不能解决问题。不过,我认为应该存在一些棘手的解决方案。
这个问题是不相关的lambda表达式或可变参数的功能 - 它也会发生,如果你简单地存储字符串:
const char* global_storage;
int main()
{
{
std::string s = "hi";
global_storage = s.c_str();
}
// !!! `global_storage` points to deleted memory!
use(global_storage);
}
你需要确保字符串活得足够长。使用std::string
代替const char*
是一个很好的起点:
std::string global_storage;
int main()
{
{
std::string s = "hi";
global_storage = std::move(s);
}
// OK, local string was moved into `global_storage`.
use(global_storage.c_str());
}
如果你确实需要使用C风格的字符串,只需将其存储在lambda /为std::string
,然后呼叫.c_str()
当你需要什么使用它,而不是在存储时使用它。
@TobySpeight:很好,我真的想用'c_str'那里。 –
当您将其存储在lambda中时,您需要将char const*
参数转换为std::string
。这是一种可能的方式,我可以提出:
#include <iostream>
#include <tuple>
using namespace std;
template<typename T, typename R = conditional_t<is_same<T, char const*>::value, string, T>>
R bar (T &&value) {return value;}
template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple_impl(std::basic_ostream<Ch,Tr>& os,
const Tuple & t,
std::index_sequence<Is...>)
{
using swallow = int[]; // guaranties left to right order
(void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}
template<class Ch, class Tr, class... Args>
decltype(auto) operator<<(std::basic_ostream<Ch, Tr>& os,
const std::tuple<Args...>& t)
{
os << "(";
print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
return os << ")";
}
template<typename...Args>
decltype(auto) foo(Args...args)
{
return [args = make_tuple(bar(args)...)]() { cout<< args; return; };
}
int main() {
string *s = new string("Hello, World!");
const char *p = s->c_str();
auto f = foo(1, p, 3.14);
delete s;
f();
return 0;
}
foo
函数返回拉姆达存储可变参数作为元组,其中每个char const*
元件被自动转换为std::string
。之后,您可以释放临时字符串。释放后调用lambda表达式现在应该是安全的。
谢谢,但它需要每个char const *参数都有新的删除过程。代码将由除我以外的其他开发人员共享,所以我想尽可能简化代码,我不能保证他们对操作内存的处理足够谨慎。无论如何,感谢您提供解决方案,我正在使用C++ 11。 –
如果由于'std :: string'中的小对象优化导致字符串足够短,它将不会使用内存分配。同样,如果你坚持在lambda中存储一个原始指针,我没有看到任何安全的方式来解决你的问题。 –
@StephenChan:如果您与其他开发人员共享代码,并且其中的一个要求是让事情变得更简单,那么您就不应该首先使用lambda表达式。现在,如果您使用的是带有'std :: string'副本的lambda表达式,那么您的编译器可能会让您感到惊讶,并优化一个额外的分配。如果表现很重要,那么一定要衡量(不要只是假设)。 – IInspectable
嗯。那么不要使用C字符串。特别是涉及所有权时。 – milleniumbug
秘密成分:'std :: string'和'[=]'。 – IInspectable
如果您知道C++ 11已经支持'std :: string'! – sehe