如何正确地将C字符串传递给variadic模板函数中的lambda表达式

这是我的复杂情况:

我有一个函数使用variadic模板和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。 我想要做的是当玩家改变他们的语言偏好 (从用户界面中选择) 时,支持标签的自动定位

我已将这些文本保存在一些文件中,并且每个文件都包含单一语言的本地化文本,它们基本上处于以下结构中:

{
    "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 (我忽略了其他两种方法的实现,因为它们对于它们的声明足够直观):

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将被保存为setOnLanguageChangeFuncLabel本地成员变量。

这在大多数情况下效果很好,情况复杂的是,本地化文本中包含格式说明符 ,例如:

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

这种格式的占位符以代码动态传递:

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

其中StringKit是一个用于格式化字符串的实用程序,它接受将传递给vsnprintf以产生输出的可变参数 。 现在你知道为什么我需要传递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风格的字符串,只需将它作为std::string存储在lambda /中, 然后在需要使用时调用.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返回将可变参数存储为元组的lambda,其中每个char const*元素自动转换为std::string 。 之后,您可以释放临时字符串。 释放后调用lambda表达式现在应该是安全的。

IdeOne.com

链接地址: http://www.djcxy.com/p/51581.html

上一篇: How to properly pass C strings to a lambda inside a variadic template function

下一篇: Perfect forward variadic arguments to lambda