25

を持っているとしstd::map<std::string, int>ます。一時std::string文字なしで C 文字列 (const char*) と比較できます。std::stringただし、おそらくメモリ割り当てが必要map::find()な一時的な を作成する必要があるようです。std::stringどうすればこれを回避できますか? 概念的には簡単ですが、STL はこれを防いでいるようです。

#include <map>

int main()
{
    std::map<std::string, int> m;
    m.find("Olaf");
}
4

4 に答える 4

15

あなたの懸念は現実のものであり、C++11 の適切な回避策はありません。

C++14 は、テンプレート化されたオーバーロードを追加することでこの問題を修正しますstd::map::find— 関連する提案はN3657です。C++14 では、プログラムは次のようになります。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <algorithm>

class std_string {
    char *m_s;
public:
    std_string() { m_s = nullptr; }
    std_string(const char* s) { puts("Oops! A new std_string was constructed!"); m_s = strdup(s); }
    ~std_string() { free(m_s); }
    std_string(std_string&& ss) = delete;
    std_string(const std_string& ss) = delete;
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;

    bool operator< (const char* s) const { return strcmp(m_s, s) < 0; }
    bool operator< (const std_string& ss) const { return strcmp(m_s, ss.m_s) < 0; }
    friend bool operator< (const char* s, const std_string& ss) { return strcmp(s, ss.m_s) < 0; }
};

int main()
{
    {
        puts("The C++11 way makes a copy...");
        std::map<std_string, int> m;
        auto it = m.find("Olaf");
    }
    {
        puts("The C++14 way doesn't...");
        std::map<std_string, int, std::less<>> m;
        auto it = m.find("Olaf");
    }
}

(std::less<>は一般化された「より小さい」コンパレーターであり、 と同等operator<です。C++03 と C++11 には、このコンパレーターの壊れたバージョンがあり、両方の引数を強制的に同じ型にします。C++14 は最終的にそうしますそうですね。)

残念ながら、委員会は、すべての C++11 コードを調べて、すべてのコンテナーを更新std::less<>してコンパレーターとして使用する必要があると判断したようです。これは、デフォルトで発生するだけではありません。この決定には正当な理由はありません。それはただの方法です。(壊れた設計に関する上記の私のコメントを参照してください。C++ には、数年後に「実際の」バージョンを導入する前に、壊れたバージョンのものを導入するという悪い習慣があります。)

C++11 の場合、std::map::findには 1 つのオーバーロード ( を受け取るもの) しかないため、回避策として型をより安価なものにconst Key&変更する必要があります。の引数を型にKey昇格させました。findKey

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <algorithm>

class std_string {
    char *m_s;
public:
    std_string() : m_s(nullptr) { }
    std_string(const char* s) : m_s(strdup(s)) { puts("Oops! A new std_string was constructed!"); }
    ~std_string() { free(m_s); }
    std_string(std_string&& ss) : m_s(nullptr) { std::swap(m_s, ss.m_s); }
    std_string(const std_string& ss) : m_s(strdup(ss.data())) { puts("Oops! A new std_string was constructed!"); }
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;

    const char* data() const { return m_s; }

    bool operator< (const char* s) const { return strcmp(m_s, s) < 0; }
    bool operator< (const std_string& ss) const { return strcmp(m_s, ss.m_s) < 0; }
    friend bool operator< (const char* s, const std_string& ss) { return strcmp(s, ss.m_s) < 0; }
};

struct string_or_ptr {
    union {
        const char* ptr;
        alignas(std_string) unsigned char str[sizeof (std_string)];
    } m_u;
    bool m_deep;

    char const* & ptr() { return m_u.ptr; }
    std_string& str() { return *reinterpret_cast<std_string*>(m_u.str); }
    char const* const & ptr() const { return m_u.ptr; }
    std_string const& str() const { return *reinterpret_cast<const std_string*>(m_u.str); }

    string_or_ptr() : m_deep(false) { ptr() = ""; }
    string_or_ptr(const char* s) : m_deep(false) { ptr() = s; }
    string_or_ptr(std_string&& s) : m_deep(true) { new ((void*)&str()) std_string(std::move(s)); }
    string_or_ptr(const std_string& s) : m_deep(true) { new ((void*)&str()) std_string(s); }
    ~string_or_ptr() { if (m_deep) str().~std_string(); }
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;


    operator const char*() const { return m_deep ? str().data() : ptr(); }

    bool operator< (const char* s) const { return strcmp((const char*)*this, s) < 0; }
    bool operator< (const std_string& ss) const { return (const char*)*this < ss; }
    bool operator< (const string_or_ptr& sp) const { return strcmp((const char*)*this, (const char*)sp) < 0; }
    friend bool operator< (const char* s, const string_or_ptr& sp) { return strcmp(s, (const char*)sp) < 0; }
    friend bool operator< (const std_string& ss, const string_or_ptr& sp) { return ss < (const char*)sp; }
};

int main()
{
    {
        puts("The C++11 way...");
        std::map<std_string, int> m;
        auto it = m.find("Olaf");
    }
    {
        puts("The C++11 way with a custom string-or-pointer Key type...");
        std::map<string_or_ptr, int> m;
        auto it = m.find("Olaf");
    }
}
于 2014-06-23T07:25:33.597 に答える
2

findを作成するために使用されたものとは異なる比較演算子を使用するように強制する方法は実際にはありませんmap。別のものをに渡すことができる場合find、両方の比較が同じ順序を提供することをどのように保証できますか?

代わりに、ケースについて考えてみてください。

char*1)あなたはあなたのプログラムを回っています。この場合、それをしないでください。代わりに使用std::stringし、必要に応じて、オリジネーションにできるだけ近い場所で1回作成します。その後、変換は必要ありません。

2)文字列リテラルを見つけようとしています。この場合、なぜキーはstring?代わりに、キーを適切な名前の列挙型にします。

enum names { OLAF };
map<names, int> m;
m.find(OLAF);

3)文字列とC文字列リテラルの両方を検索する必要があります。この場合、列挙型でインデックス付けされた文字列のグローバルルックアップテーブルを作成しますが、mainの開始時に1回作成します。次に、あなたは次のようなものを呼び出すでしょうm.find(global_strings[OLAF]);

編集:あなたはここのパフォーマンスへの影響について非常に集中/懸念しているようですstring。アプリケーションのプロファイルを作成し、stringの割り当てがアプリの時間のかなりの部分であることがわかりましたか?もちろん、これは組み込みシステム/デバイスで信じられます。

さらに、質問にC ++のタグを付けましたが、「パフォーマンスのコスト」をはるかに超えるC++の組み込み文字列機能の使用を完全に拒否しているようです。さまざまな便利な関数/メソッド/演算子を提供しますが、最も重要なのは、メモリを管理するため、間違いなく発生するであろう本当に陰湿なバグを探すのに数日または数週間を費やさないことです。

ネットワークから可変長データを読み取っている場合、パフォーマンスの違いを完全に把握することはできません。それ以外のものを使用char* buffer = new char[needed_size];すると、安全性とメモリ管理が提供されます。std::string s; s.resize(needed_size);string

于 2012-05-10T15:15:19.573 に答える
1

リテラルからの文字列の構築が実際に測定されたパフォーマンスのボトルネックである場合はstd::string、文字列またはリテラルへのポインターのいずれかを保持する の代わりに、独自のクラスを使用できます。欠点は、いくつかの余分な複雑さに加えて、コンテナーに挿入する要素へのポインターのサイズを追加することです。で必要とされるように、値は不変mapであるため、 の結果を保存しても安全であることに注意してくださいc_str

class mystring
{
    std::string  str;
    const char * value;
public:
    mystring() : value(NULL)
    {
    }
    void setString(const std::string & s)
    {
        assert(value == NULL);
        str = s;
        value = str.c_str();
    }
    void setLiteral(const char * s)
    {
        assert(value == NULL);
        value = s;
    }
    bool operator<(const mystring & rhs)
    {
        return strcmp(literal, rhs.literal) < 0;
    }
};

std::map<mystring, int> m;
mystring text;
text.setString(some_key);
m.insert(std::make_pair(text, some_data));
// ...
mystring key;
key.setLiteral("Olaf");
m[key] = new_value;
于 2012-05-10T15:49:07.730 に答える