23

Why doesn't the C++ standard include comparison operators to compare smart pointers (unique_ptr, shared_ptr, ...) with regular pointers (T*)?

Tom

update I am not looking to find out how it can be done. The question is why is it not defined as part of the C++ standard? For unique_ptr and shared_ptr such definitions would be trivial.

A use case for this is the following: Class A has a map with unique_ptr keys. unique_ptr is used for memory management. When a user of class A passed a regular pointer, a lookup is performed inside this map. Unfortunately, the standard doesn't define the comparision operators.

Tom Deseyn
  • 1,735
  • 3
  • 17
  • 29
  • 2
    Smart pointers aren't the same thing as raw pointers. Why compare apples with pears? – Tony The Lion Aug 05 '13 at 09:52
  • 2
    @TonyTheLion to find out which one is more delicious, duh. (hint: if it's a really good apple, then screw pears) :) – jalf Aug 05 '13 at 10:35
  • By the way, even if those comparisons would be possible, this still wouldn't solve your particular use case, which is limited by the `std::map`'s interface and not just by the comparisons allowed for the key type in general. FYI this same problem (searching a `std::map` with a raw pointer) has been brought up here just recently. – Christian Rau Aug 05 '13 at 10:42
  • 1
    I would compare these apples and pears with their raw pointer value. – Tom Deseyn Aug 05 '13 at 11:42
  • @ChristianRau How is the map interface limited? Do you have a link to the related question? – Tom Deseyn Aug 05 '13 at 11:44
  • @MasterT Well, [`std::map::find`](http://en.cppreference.com/w/cpp/container/map/find) only takes `std::uique_ptr`s and not arbitrary typed arguments, so having `std::unique_ptr`s be comparable to raw pointers will still not help in this case. It's only C++14 that will loosen those restrictions, I think. I'll try to find this question, since it may provide some solutions to your particular use case. – Christian Rau Aug 05 '13 at 11:46
  • 1
    @MasterT Found it: http://stackoverflow.com/q/17851088/743214 It's about `std::unordered_set`, but most of the solutions should work the same. Oh, and it seems even C++14 won't losen the interface restrictions of the associative containers. – Christian Rau Aug 05 '13 at 11:52
  • If looking up a `T*` in `std::set>` works, then you'd also expect the reverse: looking up a `std::unique_ptr` in a `std::set`. Those things tend to get complicated faster than you'd like. – MSalters Aug 05 '13 at 13:34

4 Answers4

18

Why doesn't the C++ standard include comparison operators to compare smart pointers (unique_ptr, shared_ptr, ...) with regular pointers (T*)?

The principle behind this decision is usually stated as "make your interfaces easy to use correctly and difficult/impossible to use incorrectly".

Conceptually, a smart pointer and a raw pointer are not the same.

A smart pointer imposes restrictions (i.e. "unique_ptr is a pointer, but you cannot have multiple copies"). Although they behave like pointers (to a point - if you will excuse the pun), they have different semantics.

That is, if you have:

int *p1 = new int{5};
std::unique_ptr<int> p2{new int{5}};

p1 == p2.get();

The comparison is easy to do, explicit, and makes it obvious that you are comparing apples and apples (easy to understand what's going on - you're comparing with the value of a raw pointer).

Having a custom comparison operator on the other hand would raise weird questions ("a unique_ptr is unique; how can you compare it with something else? - if it's unique, it should always be different").

utnapistim
  • 26,809
  • 3
  • 46
  • 82
  • 1
    "if it's unique, it should always be different" `auto& rp0 = p2; auto& rp1 = p2; p1 == p2` (imagine function parameters) Note there are comparison operators for `std::unique_ptr` and other `unique_ptr`s or `nullptr_t`. The former actually compare the owned pointer, i.e. `p2 == p2` :<=> `p2.get() == p2.get()`, therefore the comparison `p1 == p2.get()` would be well-defined and not suprising. – dyp Aug 05 '13 at 17:16
  • "would be well-defined and not suprising" - this is an old debate addressed by language designers in different ways (i.e. "objects are equal vs. objects have equal values"); In Java, it is the source of the string comparison problem (you compare strings with `s1.compare(s2)`, not with `s1 == s2`). If you create an `operator ==` using `std::unique_ptr` and `T*`, you _force_ all clients of your code to be unable to consider them conceptually different (because they have different types), unless they write ugly/error prone code for it (comparing the type first for example). – utnapistim Aug 05 '13 at 18:19
  • What I meant was: the `operator==` for two `unique_ptr`s is already defined solely in terms of the owned pointers. Therefore, the comparison to a raw pointer wouldn't need to behave differently. – dyp Aug 05 '13 at 18:21
  • I'd suggest that if you could do `p1 == p2` that would mean that p2 was being implicitly converted to a `int*` (or I suppose p1 could be implicitly converted to a smart pointer, but that's dangerous in a bunch of other ways) and would make it way too easy to pull an uncontrolled raw pointer out of a smart pointer. – Andre Kostur Aug 05 '13 at 18:36
  • I would argue with that - unique_ptr is a ptr. If it's not, you could do: ` template bool operator==(const unique_ptr& x, const unique_ptr& y) { return (!x && !y) || (&x == &y); } ` Then you can, for example, lookup unique_ptrs in vectors in constant time. unique_ptr is just a raw pointer with a tag in my opinion. – Denis Yaroshevskiy Jun 18 '17 at 11:59
11

You can just do smart_ptr.get() == raw_ptr, what is wrong with that?

Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • 2
    I know that works. Why doesn't the standard allow to compare directly? Consider for example, looking up in a map which has a smart pointer key type. You need to define your own comparator to get it to work properly. – Tom Deseyn Aug 05 '13 at 09:57
  • It adds clarity for me. Reading this is easier as you don't need that much information. Anyway - adding your own operator== as free function should not be a problem. – Tobias Langner Aug 05 '13 at 10:00
  • 3
    @MasterT Wrong question. Why should it? – R. Martinho Fernandes Aug 05 '13 at 10:25
  • 1
    @R. Martinho Fernandes There are no wrong questions. Just incomplete answers. – Alex Pana Dec 14 '14 at 14:12
  • 2
    E.g., suppose you have a `vector>` and you want to find a particular `T*`. Currently, you must use `std::find_if` with a lambda predicate, in order to unpack and compare the `unique_ptr`. With the proposed `operator==`-overload, a much more readable `std::find` would become possible. – pasbi Dec 08 '18 at 17:17
4

Because the comparison operators don't compare what is pointed to, only the actual pointer. And as the smart pointers have "ownership" of the actual raw pointer, any other raw pointer can't be the same as the compared-to smart pointer.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Raw pointers can/should be used by parts of the program which do not need to keep the object alive. When comparing, do you care about who keeps the object alive? – Tom Deseyn Aug 05 '13 at 10:17
  • 1
    @MasterT Yes of course you do. Otherwise why use smart pointers? Of course different operations require different meanings of equality. But the *general* meaning should definitely account for ownership (and potentially deleter). – Konrad Rudolph Aug 05 '13 at 10:25
  • 1
    @KonradRudolph Why should ownership and deleters be taken into account when comparing smart pointers? Ownership is about when to delete and deleters are about how to delete. In my opinion, there is no semantic relationship between deleting and comparing. – Tom Deseyn Aug 05 '13 at 11:35
-1

The main reason is probably because if you are using the standard smart pointers, there shouldn't be any raw pointers to the object. Having a raw pointer to the object is almost a guarantee that at some point during maintenance, someone will end up creating a second smart pointer from it, with disasterous consequences. (The one exception is a null pointer, and the standard does allow comparison with a null pointer constant.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    If one part of the program doesn't need to know about the lifetime of objects, I tend to use regular pointers in the class interfaces. See http://stackoverflow.com/questions/7657718/when-to-use-shared-ptr-and-when-to-use-raw-pointers – Tom Deseyn Aug 05 '13 at 10:20
  • 7
    Just hammer home that any raw pointer is non-owning, it should be the default assumption about them with C++11. – Xeo Aug 05 '13 at 10:24
  • @Xeo The usual case is that there are no owning pointers, period, and there is no use for any smart pointers. The very concept of a pointer owning anything is a bit weird; pointers are very low level objects, with no real behavior or responsibilities. Most applications will have no use for any smart pointers. On the other hand, mixing `std::shared_ptr` and raw pointers is a recipe for disaster. If you're using `std::shared_ptr`, then you shouldn't have a raw pointer to the object (which means no member functions, of course, since `this` is a raw pointer). – James Kanze Aug 05 '13 at 10:39
  • @MasterT Anyone with a pointer to an object needs to be informed if that object's lifetime ends. But that's normally solved by the observer pattern, without any smart pointers. – James Kanze Aug 05 '13 at 10:40
  • Non-owning references should be raw pointers. In the end, someone owns the pointer and at that location you take care of memory management for example using smart pointers. The main reason ordering operations are present in smart pointers is to put them in containers like sets or maps. When confronted with a raw pointer (e.g. observer pattern wise) you want to compare that raw pointer to the pointer you own, which is why I put this question here. – Tom Deseyn Aug 05 '13 at 11:41
  • @MasterT In the end, a lot of different objects may own pointers to other objects. But in most cases, no one "owns" the object themselves; the object will be created and destroyed in reaction to external events. That's why we're allocating it dynamically to begin with, after all. – James Kanze Aug 05 '13 at 13:06
  • Example where you would need this: if objects of class A contain a collection of A shared pointers and you want to avoid not putting a `this` shared pointer to the collection. – Matthias Feb 18 '17 at 18:36