简体   繁体   English

如何正确地将C字符串传递给可变参数模板函数内的lambda

[英]How to properly pass C strings to a lambda inside a variadic template function

Here is my complex situation: 这是我的复杂情况:

I have a function using variadic template and lambda: 我有一个使用variadic模板和lambda的函数:

template<typename...Args>
void foo(Args...args) {
    // the arguments are passed to the lambda
    _func = [args...](){ do_sth(args...); };
}

On observing specific events, the lambda _func would be fired. 在观察特定事件时,将触发lambda _func My problem is, some of the arguments I passed are C strings, they could be pointing to temporary std string like this: 我的问题是,我传递的一些参数是C字符串,它们可能指向临时的std字符串,如下所示:

const char *pStr = __temp_std__string__.c_str();

Given I call foo(pStr); 鉴于我打电话给foo(pStr); , and when _func is called, the temporary string that pStr is pointing to, has been released. ,当_func时,pStr指向的临时字符串已被释放。 I would like to know whether there exists a generic way to handle this. 我想知道是否存在处理此问题的通用方法。 I am using C++11. 我正在使用C ++ 11。

EDIT: 编辑:

Perhaps I should post my whole story, as many of you advise to pass std::string instead of C strings, there are reasons that I can't escape from it. 也许我应该发布我的整个故事,因为你们许多人建议传递std :: string而不是C字符串,有理由我无法摆脱它。

I am developing games using cocos2d-x, which deploys C++11. 我正在使用cocos2d-x开发游戏,它部署了C ++ 11。 What I want to do is to support auto-localisation of labels when players change their preferences of languages (selected from a UI). 我想要做的是当玩家改变他们的语言偏好 (从UI中选择) 时支持标签的自动本地化

I have saved the text in a couple of files, and each of them contains the localised text of a single language, they are basically under the following structure: 我已将文本保存在几个文件中,并且每个文件都包含单个语言的本地化文本,它们基本上属于以下结构:

{
    "key1" : "_localized_text1_",
    "key2" : "_localized_text2_",
    ...
}

The idea is to observe the event on change of language's preference (through a notification), and I would get a key indicating that language from it, so as to fetch the localised text from the proper file. 我的想法是观察语言偏好变化的事件(通过通知),我会得到一个表示该语言的密钥,以便从正确的文件中获取本地化的文本。 Here is the way how I implement it in the object class Label : 以下是我在对象类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);
};

the core function is setLocalizeString (I skip the implementations of the other 2 methods as they are intuitive enough from their declaration): 核心函数是setLocalizeString (我跳过其他两个方法的实现,因为它们的声明足够直观):

void Label::setLocalizeString(const std::string& key) {
    // the callback lambda
    auto callback = [=](Notification *pNotification){
      setString(LOCALIZED_STRING(key)); 
    }

    // assign the lambda
    setOnLanguageChangeFunc(callback);
}

where LOCALIZED_STRING is the macro helper of fetching localised string with a key; 其中LOCALIZED_STRING是用键获取本地化字符串的宏助手; and the lambda callback would be saved as local member variable of Label in setOnLanguageChangeFunc . 并且lambda callback将在setOnLanguageChangeFunc保存为Label本地成员变量。

This works great in most cases, what makes the situation complicated is, there are format specifiers involved in the localised text, for example: 这在大多数情况下都很有效,使情况变得复杂的是,本地化文本中涉及格式说明符 ,例如:

{
    ...
    "keyN" : "%s eats %d cookies",
    ...
}

Such format placeholders are passed dynamically in codes: 这种格式占位符在代码中动态传递:

// formatStr = "Tom eats 5 cookies"
std::string formatStr = StringKit::stringWithFormat("%s eats %d cookies", "Tom", 5);

where StringKit is a utility to format the string, and it accepts variadic arguments which would be passed to vsnprintf to yield the output. 其中StringKit是一个格式化字符串的实用程序,它接受可变参数,这些参数将传递给vsnprintf以产生输出。 Now you know why I need to pass C string and not std::string, its just due to the underlying method to format string. 现在你知道为什么我需要传递C字符串而不是std :: string,这只是因为格式化字符串的基础方法。

Now I have to modify Label::setLocalizeString so that it could digest the possible variadic arguments: 现在我必须修改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);
}

And this is its use case: 这是它的用例:

// on changing language, the label would display "Tom eats 5 cookies"
pLabel->setLocalizeString("keyN", "Tom", 5);

This case would work like a charm as that C string argument is global, but when it is passed from a temporary std::string : 这个案例就像一个魅力,因为C字符串参数是全局的,但是当它从临时的std :: string传递时:

std::string tempStr = "Tom";
pLabel->setLocalizeString("keyN", tempStr.c_str(), 5);

The C string "Tom" would lose the value on calling the lambda callback, since the pointed std::string, has been gone. 由于指向的std :: string已经消失,因此C字符串“Tom”将失去调用lambda回调的值。

I have tried several ways, like playing with tuple things, or capturing a wrapper class of basic types in the lambda, but none of them could solve the problem. 我尝试了几种方法,比如玩元组的东西,或者在lambda中捕获一个基本类型的包装类,但它们都不能解决问题。 However, I think there should exist some tricky solutions. 但是,我认为应该存在一些棘手的解决方案。

This problem is not related to lambdas or variadic functions - it also occurs if you simply store the string: 此问题与lambdas或可变参数函数无关 - 如果只是存储字符串,也会出现此问题:

const char* global_storage;

int main()
{
    {
        std::string s = "hi";
        global_storage = s.c_str();
    }

    // !!! `global_storage` points to deleted memory!
    use(global_storage);
}

You need to make sure the string lives long enough. 你需要确保字符串足够长。 Using std::string instead of const char* is a great starting point: 使用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());
}

If you really need to use a C-style string, just store it in the lambda/whatever as a std::string , then call .c_str() when you need to use it, not when storing it. 如果你真的需要使用C风格的字符串,只需将它存储在lambda / whatever中作为std::string然后在需要使用它时调用.c_str() ,而不是在存储它时。

You need to convert your char const* arguments to std::string when storing it in lambda. 在将它存储在lambda中时,需要将char const*参数转换为std::string This is one possible way, i can propose: 这是一种可能的方式,我可以建议:

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

Function foo returns lambda that stores variadic arguments as tuple, where each char const* element is converted to std::string automatically. 函数foo返回lambda,它将可变参数存储为元组,其中每个char const*元素自动转换为std::string After that you can free temporary string. 之后你可以释放临时字符串。 It's now should be safe to call that lambda after freeing. 现在释放后释放lambda应该是安全的。

IdeOne.com IdeOne.com

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在可变参数模板类中将lambda表达作为参数传递给mermber函数 - how to pass lambda express as parameter into mermber function in variadic template class 在variadic模板中从lambda隐式转换为std :: function - implicit convert from lambda to std::function inside variadic template 变体模板函数接受lambda - Variadic template function accepting lambda 是否可以展开可变参数(lambda)模板并将这些函数的返回值传递给另一个可变参数函数? - Is it possible to unfold a variadic (lambda) template and the pass those functions return value to another variadic function? C ++如何将可变参数函数模板的参数包-&gt;包装到lambda中 - C++ how to wrap a variadic function template's parameter pack -> into a lambda 如何在 C++ 中专门化一个可变参数模板函数? - How to specialize a variadic template function in c++? 存储可变参数模板参数并将其传递给lambda - Store variadic template arguments and pass them to lambda 将容器传递给可变参数模板 function - Pass container to a variadic template function 将std :: function与lambda和variadic模板一起使用 - Use std::function with lambda and variadic template 是否可以从函数模板返回可变参数lambda? - Is it possible to return a variadic lambda from a function template?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM