2016-09-30 28 views
1

这里是我的复杂局面:如何正确传递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中捕获基本类型的包装类,但是他们都不能解决问题。不过,我认为应该存在一些棘手的解决方案。

+2

嗯。那么不要使用C字符串。特别是涉及所有权时。 – milleniumbug

+0

秘密成分:'std :: string'和'[=]'。 – IInspectable

+2

如果您知道C++ 11已经支持'std :: string'! – sehe

回答

3

这个问题是不相关的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()当你需要什么使用它,而不是在存储时使用它。

+0

@TobySpeight:很好,我真的想用'c_str'那里。 –

0

当您将其存储在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表达式现在应该是安全的。

IdeOne.com

+0

谢谢,但它需要每个char const *参数都有新的删除过程。代码将由除我以外的其他开发人员共享,所以我想尽可能简化代码,我不能保证他们对操作内存的处理足够谨慎。无论如何,感谢您提供解决方案,我正在使用C++ 11。 –

+1

如果由于'std :: string'中的小对象优化导致字符串足够短,它将不会使用内存分配。同样,如果你坚持在lambda中存储一个原始指针,我没有看到任何安全的方式来解决你的问题。 –

+0

@StephenChan:如果您与其他开发人员共享代码,并且其中的一个要求是让事情变得更简单,那么您就不应该首先使用lambda表达式。现在,如果您使用的是带有'std :: string'副本的lambda表达式,那么您的编译器可能会让您感到惊讶,并优化一个额外的分配。如果表现很重要,那么一定要衡量(不要只是假设)。 – IInspectable

相关问题