56

I have a question on how pointers to a custom object are handled when used as Keys in an map. More specifically if I define

std::map< CustomClass*, int > foo;

Would the default C++ implementation work to handle these pointers? Or do I need to define a custom comparator function to handle it? In general, is it good practice to use pointers to objects as keys?

Ammar Husain
  • 1,789
  • 2
  • 12
  • 26
  • 3
    Anything that supports `less` can be used in a map. See http://stackoverflow.com/questions/1098966/universal-less-for-pointers-in-c-standard – Mark Ransom Aug 04 '14 at 16:11
  • 1
    One possible downside to using pointers as keys in maps is that the order of the items is non-deterministic (depends on their memory address). If you need to iterate over the items in a deterministic order, you can't use pointers as keys. This can be important in multiplayer games. – jhoffman0x Aug 04 '14 at 16:14

5 Answers5

83

The default implementation will compare the addresses stored by the pointers, so different objects will be considered as different keys. However, the logical state of the object will not be considered. For example, if you use std::string * as the key, two different std::string objects with the same text of "Hello" would be considered a different key! (When stored in the map by their addresses)

It's ok to use pointers as keys so long as you understand the important difference above.

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
  • 2
    Yes thats a great point! The assumption is my case though are pointers to unique objects. – Ammar Husain Aug 04 '14 at 16:14
  • If comparing pointers is undefined/unspecified behaviour; how comes you can use pointers as keys for a map? – user13947194 Aug 02 '23 at 00:22
  • @user13947194 `std::less` is well defined for pointers and guaranteed to give a global total order while plain comparison operator `<` is not (the behavior is unspecified in C++) – Weijun Zhou Aug 08 '23 at 11:39
  • 1
    @WeijunZhou yes I had recently found that out. I think I am going to create a proper Cpp book/ tutorial that will teach beginners the right thing from day one. – user13947194 Aug 10 '23 at 00:18
36

Pointers will be handled but compared as pointers (memory order). You have to pass custom less functor if you wish to compare the objects:

template<class T> struct ptr_less 
{
    bool operator()(T* lhs, T* rhs) 
    {
        return *lhs < *rhs; 
    }
};
map<Object*, int, ptr_less<Object>> myMap;
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
firda
  • 3,268
  • 17
  • 30
  • 4
    This should be chosen as the correct answer. Comparing pointers is (almost always surely) meaningless and will certainly lead to bugs that are extremely hard to detect. – S. K. Oct 21 '16 at 03:32
  • 4
    It's fine if you just want to use the pointer as a key for a quick lookup into a map. Often maps are just used as lookup tables and the order isn't terribly relevant. – Andy Mar 09 '18 at 19:56
  • 1
    @Andy: I never said it is not, if you know what you are doing (I said: Pointers will be handled but compared as pointers = memory order). I only added possible solution if you expected to compare the objects, nothing more, nothing less. – firda Mar 09 '18 at 21:05
  • 3
    No offense @firda - comment was meant for comment above mine. – Andy Mar 09 '18 at 21:38
  • The answer should have been: you can, but you either should not (use unordered_map instead) or you don't know what are you doing (again, either unordered_map or the functor). The only possible counter argument I can think of is: it is going to be small anyway (OK, in that case, map is acceptable). – firda May 22 '23 at 16:30
  • Hmm. Isn''t comparison of pointers not in array bounds unspecified behaviour? – user13947194 Aug 02 '23 at 00:20
11

C++ standard provided specialisation of std::less for pointers, so yes you can safely use them as map keys etc.

Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
6

Leaving aside from the legality of this and any possible semantic misunderstandings, addressed already, I cannot think of any reason to use std::map here rather than std::unordered_map. There are early intercepts of this in Boost and Visual C++ if you are on a pre-C++11 compiler.

Since you appear to be using a pointer to represent a unique object, something like boost::flyweight could be applicable.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
6

Pointers can be used as keys but especially with a std::map (or std::set) I would not advise it. The behavior of the program is not deterministic i.e. when one iterates over the map the order in which the items in the map are iterated is not guaranteed to be the same. It really depends on the memory address of the object (key). Take a look at this example, as you can see irrespective of the insertion order into the map the items are iterated in a deterministic way when the key is a string rather than a pointer.

http://ideone.com/VKirct

#include <iostream>
#include <map>
using namespace std;

class SomeClass {
    public:
    SomeClass(const std::string& name): m_name(name) {}
    std::string GetName()const {return m_name; }
    bool operator <(const SomeClass& rhs) const { return m_name < rhs.m_name; }
    private:
    std::string m_name;
};

auto print_seq  = [](const auto& seq) { for (const auto& itr: seq) {std::cout << itr.second << " , ";} std::cout << std::endl;};

int main() {
    // your code goes here
    std::map<SomeClass*, std::string> pointer_keyed_map;
    SomeClass s3("object3");
    SomeClass s1("object1");
    SomeClass s2("object2");
    pointer_keyed_map.insert(std::make_pair(&s1, s1.GetName()));
    pointer_keyed_map.insert(std::make_pair(&s2, s2.GetName()));
    pointer_keyed_map.insert(std::make_pair(&s3, s3.GetName()));
    std::cout << "Pointer based keys: object order" << std::endl;
    print_seq(pointer_keyed_map);

    std::map<SomeClass, std::string> int_keyed_map;
    int_keyed_map.insert(std::make_pair(s3, s3.GetName()));
    int_keyed_map.insert(std::make_pair(s1, s1.GetName()));
    int_keyed_map.insert(std::make_pair(s2, s2.GetName()));
    std::cout << "String based keys: object order" << std::endl;
    print_seq(int_keyed_map);
    return 0;
}
Chenna V
  • 10,185
  • 11
  • 77
  • 104