5

According to SGI's doc on associative containers, "Since elements are stored according to their keys, it is essential that the key associated with each element is immutable". I sometimes use a pointer as a key to std::map, since, although the pointed object might be mutable, the pointer itself is constant.

QPointer is technically an object mimicking a pointer, and Qt's doc says that we can use QPointers exactly like pointers. Since the QPointer object itself may change during execution, can it still be used as the key to a std::map container?

Edit 1 : I can't use a QMap, I have to stick to std::map.
Edit 2 : The code compiles when I use a QPointer. The question is about whether I should expect unpleasant surprises at runtime.

demonplus
  • 5,613
  • 12
  • 49
  • 68
Fred
  • 4,894
  • 1
  • 31
  • 48

3 Answers3

7

No, this is not safe, because QPointer<T> may change to NULL when a QObject is destroyed. (QPointer is similar to std::weak_ptr.) So this code would produce Undefined Behavior:

class CopyableWidget : public QWidget {
  Q_OBJECT;
public:
  CopyableWidget(Widget* parent = 0) : QWidget(parent) {}
  CopyableWidget(const CopyableWidget& w, Widget* parent = 0) : QWidget(parent) {}
};

std::vector<CopyableWidget> v(2); // Two default-constructed widgets
std::map<QPointer<CopyableWidget>, int> wid_values;
wid_values[&v[0]] = 1;
wid_values[&v[1]] = 2;
// wid_values contains { {&v[0], 1}, {&v[1], 2} }
v.resize(1);
// The QPointer in wid_values constructed from &v[1] now acts like NULL.
// wid_values contains { {&v[0], 1}, {NULL, 2} }
// But &v[0] > NULL (on most platforms). Class invariant was violated!
aschepler
  • 70,891
  • 9
  • 107
  • 161
0

I take it (from the principle of least surprise) that a QPointer won't "magically" change where it points if you don't reassign it and don't delete the underlying object, so it should be safe.

(I must add that I've seen Qt violate the principle of least surprise several times, so test to be sure.)

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
0

It is undefined behavior to compare pointers from different allocation blocks for anything other than equality/inequality. I.e. After allocating a block with new or malloc, you can walk through the buffer and test if your pointer is less than the end of the buffer. But you can't do two separate news/mallocs and compare the resulting pointers for anything other than equality/inequality.

Well... you can but you may not like the results.

Brad
  • 861
  • 5
  • 11
  • 2
    But associative containers use `std::less`, which is defined to work for any pointer type. – Mike Seymour Mar 08 '11 at 21:47
  • Perhaps the doc I'm using is out of date. I'm looking at the line the section of the standard that says that the relational operators between pointers give unspecified behavior unless they point within the same allocation. Is that no longer true? – Brad Mar 08 '11 at 21:57
  • 2
    associative containers don't use relational operators, they use `std::less`. That is specifically defined to give a total ordering for pointers, even if the relational operators don't. – Mike Seymour Mar 08 '11 at 22:02
  • Thanks for the clarification. All this time, I've just been looking at 5.9 and applying that rule to maps. Its a good day when you learn something. – Brad Mar 08 '11 at 22:19