std :: mapのキーとしてchar *を使用する


81

次のコードが機能しない理由を理解しようとしています。これは、キータイプとしてchar *を使用する場合の問題であると想定していますが、どのように解決できるのか、なぜ発生するのかわかりません。私が(HL2 SDKで)使用する他のすべての関数は使用するchar*ので、使用std::stringすると多くの不必要な複雑さが発生します。

std::map<char*, int> g_PlayerNames;

int PlayerManager::CreateFakePlayer()
{
    FakePlayer *player = new FakePlayer();
    int index = g_FakePlayers.AddToTail(player);

    bool foundName = false;

    // Iterate through Player Names and find an Unused one
    for(std::map<char*,int>::iterator it = g_PlayerNames.begin(); it != g_PlayerNames.end(); ++it)
    {
        if(it->second == NAME_AVAILABLE)
        {
            // We found an Available Name. Mark as Unavailable and move it to the end of the list
            foundName = true;
            g_FakePlayers.Element(index)->name = it->first;

            g_PlayerNames.insert(std::pair<char*, int>(it->first, NAME_UNAVAILABLE));
            g_PlayerNames.erase(it); // Remove name since we added it to the end of the list

            break;
        }
    }

    // If we can't find a usable name, just user 'player'
    if(!foundName)
    {
        g_FakePlayers.Element(index)->name = "player";
    }

    g_FakePlayers.Element(index)->connectTime = time(NULL);
    g_FakePlayers.Element(index)->score = 0;

    return index;
}

14
時々正しいことをすることは最初は痛いです。std:string一度使用するようにコードを変更し、その後は満足してください。
ビョルンポレックス2010

1
どんな合併症?char *からstd :: stringへの暗黙の変換があります。
tenfour 2010

1
char*マップキーとして使用しないでください。理由は私の答えを参照しください。
sbi 2010

これは、を使用しないことによって引き起こされる不必要な合併症のようstd::stringです。
Pedro d'Aquino

バイナリキーを使用するために、キーの値が別のキーよりも小さいことを知る代わりに、マップがキーが等しいかどうかを知る必要がないかどうかはわかりません。
codeMinion 2013

回答:


140

マップに比較ファンクターを指定する必要があります。そうしないと、ポインターが指すヌル終了文字列ではなく、ポインターが比較されます。一般に、これは、マップキーをポインタにしたい場合にいつでも当てはまります。

例えば:

struct cmp_str
{
   bool operator()(char const *a, char const *b) const
   {
      return std::strcmp(a, b) < 0;
   }
};

map<char *, int, cmp_str> BlahBlah;

2
実際、彼&std::strcmpは3番目のテンプレートパラメータとしてを渡すことができます
Armen Tsirunyan 2010

23
いいえ、strcmp正、ゼロ、または負の整数を返します。マップファンクターは、より小さい場合はtrueを返し、それ以外の場合はfalseを返す必要があります。
aschepler 2010

4
@Armen:3番目のテンプレートパラメータはf(a,b) = a<b、ではなく、のようなものを期待しているので、うまくいくとは思いませんf(a,b) = (-1 if a<b, 1 if a>b, 0 else)
kennytm 2010

28
ああ、すみません、私の悪い、投稿する前に考えていませんでした。コメントをそこにとどめて、私の祖先に恥をかかせてください:)
Armen Tsirunyan 2010

2
私がテストしたように、bool operator()(char const * a、char const * b)const {blabla
ethanjyx

45

文字列ではなくchar*まったく同じポインタを使用してマップにアクセスすることが完全に100%確実でない限り、使用することはできません。

例:

char *s1; // pointing to a string "hello" stored memory location #12
char *s2; // pointing to a string "hello" stored memory location #20

で地図にアクセスすると、で地図にs1アクセスするのとは異なる場所になりs2ます。


5
受け入れられた回答で説明されているように、独自のコンパレータを定義しない限り。
Lukas Kalinski 2018年

23

2つのCスタイルの文字列は、内容は同じでもアドレスが異なる場合があります。そしてmap、それは内容ではなくポインタを比較します。

に変換するコストはstd::map<std::string, int>あなたが思っているほどではないかもしれません。

ただし、本当にconst char*マップキーとして使用する必要がある場合は、次を試してください。

#include <functional>
#include <cstring>
struct StrCompare : public std::binary_function<const char*, const char*, bool> {
public:
    bool operator() (const char* str1, const char* str2) const
    { return std::strcmp(str1, str2) < 0; }
};

typedef std::map<const char*, int, StrCompare> NameMap;
NameMap g_PlayerNames;

情報をありがとう。私の経験に基づいて、std :: stringに変換することを強くお勧めします。
user2867288 2014

8

で動作させることはできますがstd::map<const char*, int>、非constポインタを使用しないでください(constキーに追加されていることに注意してください)。マップがそれらをキーとして参照している間は、これらの文字列を変更してはなりません。(マップはキーを作成することconstでキーを保護しますが、これはポインターを構成するだけであり、それが指す文字列は構成しません。)

でも、単純に使ってみませんstd::map<std::string, int>か?それは頭痛の種なしで箱から出して動作します。


8

char *を使用することと文字列を使用することを比較しています。それらは同じではありません。

Achar *はcharへのポインタです。最終的には、その値がの有効なアドレスとして解釈される整数型ですchar

文字列は文字列です。

コンテナは正しく機能しますが、キーがachar *で、値がint。であるペアのコンテナとして機能します。


1
ポインタが長整数である必要はありません。長整数がポインタよりも小さいプラットフォーム(win64など:-))があります。また、ポインタと整数が異なるレジスタにロードされ、異なる方法で処理される、よりあいまいなプラットフォームもあると思います。他の方法。C ++では、ポインターが整数型に変換可能である必要があります。これは、十分に小さい整数をポインターにキャストできることを意味するのではなく、ポインターの変換から得た整数のみをキャストできることに注意してください。
Christopher Creutzig 2012

@ChristopherCreutzig、コメントありがとうございます。それに応じて答えを編集しました。
ダニエルダラナス2012

2

他の人が言うように、この場合はおそらくchar *の代わりにstd :: stringを使用する必要がありますが、それが本当に必要な場合は、原則としてポインタをキーとして使用しても問題はありません。

このコードが機能しないもう1つの理由は、マップで使用可能なエントリを見つけたら、同じキー(char *)を使用してそのエントリをマップに再挿入しようとするためだと思います。そのキーはすでにマップに存在するため、挿入は失敗します。map :: insert()の標準は、この動作を定義します...キー値が存在する場合、挿入は失敗し、マップされた値は変更されません。その後、とにかく削除されます。最初に削除してから、再挿入する必要があります。

char *をstd :: stringに変更しても、この問題は残ります。

私はこのスレッドがかなり古いことを知っています、そしてあなたは今までにそれをすべて修正しました、しかし私は誰もこの点を指摘しているのを見ませんでした。


0

複数のソースファイルで要素を見つけようとすると、char *をマップキーとして使用するのに苦労しました。要素が挿入されている同じソースファイル内のすべてのアクセス/検索が正常に機能します。ただし、別のファイルのfindを使用して要素にアクセスしようとすると、マップ内に確実に含まれている要素を取得できません。

Plaboが指摘したように、別のcppファイルでアクセスされた場合、ポインター(すべてのコンパイル単位には独自の定数char *があります)はまったく同じではないことが原因であることがわかりました


0

std::map<char*,int>デフォルトstd::less<char*,int>を使用してchar*キーを比較し、ポインタ比較を行います。ただし、次のように独自のCompareクラスを指定できます。

class StringPtrCmp {
    public:
        StringPtrCmp() {}

    bool operator()(const char *str1, const char *str2) const   {
        if (str1 == str2)
            return false; // same pointer so "not less"
        else
            return (strcmp(str1, str2) < 0); //string compare: str1<str2 ?
    }
};

std::map<char*, YourType, StringPtrCmp> myMap;

char *ポインタが有効であることを確認する必要があることに注意してください。std::map<std::string, int>とにかく使うことをお勧めします。


-5

それは、比較(サポート限り、任意のキータイプを使用しても問題ありません<>==)および割り当てが。

言及すべき1つのポイント-テンプレートクラスを使用していることを考慮に入れてください。その結果、コンパイラは、2つの異なるインスタンスが生成されますchar*とをint*。一方、両方の実際のコードは実質的に同じです。

したがってvoid*、キータイプとしてaを使用し、必要に応じてキャストすることを検討します。これは私の意見。


8
キーはサポートする必要があるだけ<です。しかし、それを役立つ方法で実装する必要があります。ポインタはありません。最新のコンパイラは、同一のテンプレートインスタンスをフォールドします。(私は確かにVCがこれを行うことを知っています。)void*測定が多くの問題を解決するためにこれを示さない限り、私は決して使用しません。型安全性の放棄は、時期尚早に行われるべきではありません。
sbi 2010
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.