2016-09-30 4 views
1

である私の複雑な状況:適切に可変引数テンプレート関数内のラムダにCの文字列を渡す方法ここで

私は可変長テンプレートとラムダ使用して機能を持っています、特定のイベントを観察する上で

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

をラムダ_funcが発射される。 私の問題は、私は渡された引数のいくつかは、彼らはこのように一時的なSTDの文字列を指すことができ、Cの文字列で、次のとおりです。

const char *pStr = __temp_std__string__.c_str(); 

私はfoo(pStr);を呼び出し、_funcが呼び出されたときに、一時的な文字列がのpStrがあることを考えます指し示す、リリースされました。これを処理する一般的な方法が存在するかどうかを知りたいと思います。私はC++ 11を使用しています。

EDIT:あなたの多くは、STDを渡すために助言するよう

おそらく、私は、私の全体的な話を投稿する必要があります::代わりにC文字列の文字列を、私はそれから逃れることができない理由があります。

私はC++ 11をデプロイするcocos2d-xを使ってゲームを開発しています。私がしたいのは、プレイヤーが言語の好みを変更するときに、ラベルの自動ローカライゼーションをサポートするためのです(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は、ローカライズされた文字列をキーでフェッチするマクロヘルパーです。ラムダcallbackは、ローカルメンバー変数LabelとしてsetOnLanguageChangeFuncに保存されます。

これは、ほとんどのケースで素晴らしい作品は、どのような状況が複雑になることは、ローカライズされたテキストに関与書式指定があり、例えば:

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

このような形式のプレースホルダは、コードに動的に渡されます。

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

は、文字列の書式を設定するためのユーティリティであり、それは、に渡される可変長引数を受け付けます出力を得るために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"は、指摘されたstd :: stringがなくなったので、ラムダコールバックの呼び出し時に値を失います。

タプルのもので遊んだり、ラムダの基本型のラッパークラスを取り込んだりするなど、いくつかの方法を試しましたが、いずれも問題を解決できませんでした。しかし、私はいくつかのトリッキーな解決策が存在すべきだと思います。

+2

よくduh。 Cの文字列を使用しないでください。特に所有権が関与している場合。 – milleniumbug

+0

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

+2

あなたはC++ 11が 'std :: string'を既にサポートしていることを知りたいと思うでしょう! – sehe

答えて

3

この問題は、ラムダまたは可変長引数の関数とは関係ありません - あなたは、単に文字列を格納する場合、それはまた、発生します。

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()); 
} 

あなたは本当にただstd::stringとしてラムダ/何に保管し、Cスタイルの文字列を使用する必要がある場合は、その後あなたが必要とするコール.c_str()それを保管するときではなく、使用する。

+0

@TobySpeight:良いキャッチ、私は実際に 'c_str'を使いたいそこ。 –

0

ラムダに格納するときは、char const*引数をstd::stringに変換する必要があります。これは、1つの可能な方法であり、iは、提案することができる:各char const*要素を自動的std::stringに変換されるタプルとして可変長引数を格納ラムダを返すfoo

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

機能。その後、一時的な文字列を解放することができます。今すぐラムダを解放した後に呼び出すのは安全です。

IdeOne.com

+0

ありがとうございますが、すべてのchar const *引数にnew-deleteプロセスが必要です。コードは私以外の他の開発者によって共有されるので、私はできるだけシンプルにしておきたいと思います。メモリの取り扱いを操作するのに十分な注意を払うことは保証できません。とにかくソリューションを提供してくれてありがとう、私はC++ 11を使っています。 –

+1

文字列が 'std :: string'の小さなオブジェクトの最適化のために十分に短い場合、メモリ割り当ては使用されません。あなたがラムダに生ポインタを格納することを主張するなら、あなたの問題を回避するための安全な方法は全くありません。 –

+0

@StephenChan:他の開発者とコードを共有していて、可能なものよりも単純なものにすることが1つの要件であれば、まずラムダを使ってはいけません。今では 'std :: string'コピーでlambdaを使用している場合、あなたのコンパイラはあなたを驚かせるかもしれません。パフォーマンスが重要な場合は、常に測定してください(ただ仮定しないでください)。 – IInspectable

関連する問題