3

I have a simple question regarding const_cast and best practices regarding STL containers. Consider the following where class Foo has a private STL std::map from Widget* to int:

Declaration:

#include <map>  
using std::map;

class Widget;

class Foo {
public:
     Foo(int n);
     virtual ~Foo();

     bool hasWidget(const Widget&);

private:
     map<Widget*,int> widget_map;
};

Definition:

#include <map>
#include "Foo.h"
#include "Widget.h"

using std::map;

Foo::Foo(int n)
{
     for (int i = 0; i < n; i++) {
          widget_map[new Widget()] = 1;
     }
}

Foo::~Foo()
{
     map<Widget*, int>::iterator it;
     for (it = widget_map.begin(); it != widget_map.end(); it++) {
          delete it->first;
     }
}

bool Foo::hasWidget(const Widget& w)
{
     map<Widget*, int>::iterator it;
     it = this->widget_map.find(const_cast<Widget*>(&w));
     return ( ! ( it == widget_map.end() ) );
}

Given that hasWidget takes a reference to const as its parameter, the constness needs to be cast away when calling map::find (wiget_map being from Wiget* to int). As far as I can tell, this approach is both sensible and desirable -- but I'm reluctant to accept it as such without feedback from more experienced C++ programmers.

It seems to me that this is one of the few cases of using const_cast appropriately given that we're passing the result of the cast to an STL method. Am I correct?

I realise that other permutations of this question have been posed already (for example, const_cast for vector with object) but none seem to directly address the above.

Thanks in advance.

Community
  • 1
  • 1
Marc
  • 233
  • 2
  • 7
  • 1
    You have a map from pointer to `Widget`, but the `Widget` s are created dynamically by `Foo` and the map is private. How is any client of the class magically going to end up with a `Widget` reference that happens to be in the map? If `Foo` owns the `Widget` s, why doesn't it use a container that expresses this ownership? I don't understand the point of the map. What is the meaning of the integer? – CB Bailey Nov 30 '10 at 11:08
  • @Charles: it turns out (in response to a question I asked) that this isn't the complete program ;-) – Steve Jessop Nov 30 '10 at 11:10
  • http://msdn.microsoft.com/en-us/library/92cwhskb.aspx MSDN seems to think that map::find takes a const Key&, so in this code, you cast a const Widget* to a Widget*, which is then promoted back to a const Widget* as soon as you pass it to find(). – Puppy Nov 30 '10 at 11:10
  • 1
    @DeadMG: if `Key` is `Widget*` then `const Key` isn't `const Widget *`, it's `Widget *const`. This is why we prefer typedefs to macros :-) – Steve Jessop Nov 30 '10 at 11:20
  • @DeadMG: `map::find` _does_ take a `const Key&` which in this case is a `Widget* const&`. The cast is "necessary" because you can't implicitly convert from a `const Widget*` to `Widget *` which would then be allowed to bind to a `Widget * const &`. – CB Bailey Nov 30 '10 at 11:22
  • I find it very odd and suspect that the key in your map is a `Widget*`. Typically the `Widget*` would be the payload, not the key. – John Dibling Nov 30 '10 at 14:24

6 Answers6

1

I think I'm going to fall in the 'subjective and argumentative' through my answer, but I'll give it a shot...

I'm not horrified by the const_cast, but I'm skeptical on your design. The member function hasWidget takes its parameter by const ref : what does this say to the client ? From a client point of view, if I didn't know the implementation, I would probably think that each Widget is compared by value with the parameter. For me, the interface does not reflect the actual behavior, which compares the Widget by address.

For example, the current signature allows a temporary Widget to be passed, although the return value could never be true in this case. I would personally change the signature to (note that I added a const) :

bool hasWidget(const Widget *) const;
icecrime
  • 74,451
  • 13
  • 99
  • 111
  • Seems like there's a trade-off here to me. Using your signature (with a `const Widget*`), it's clear to the client that the pointed-to `Widget` doesn't get modified, which is good, but then you end up needing an internal `const_cast`. That's not necessarily bad, but it's at least mildly annoying. Using my signature (with a `Widget*`) avoids the `const_cast`, but makes it less clear to the client that the pointed-to `Widget` won't be changed (although you could probably guess from the name of the function). Ya win some, ya lose some I guess :) – Stuart Golodetz Nov 30 '10 at 11:53
  • (I think on balance I probably prefer yours, actually...) – Stuart Golodetz Nov 30 '10 at 11:53
1

Why not use a map<const Widget*,int>? You don't seem to ever modify the Widget pointed to by any of the keys in your map.

Assuming there's a good reason then yes, I think you're right. When calling code which is guaranteed not to modify the referand of the pointer, it's safe to cast away const. Because of the way containers of pointers are templated, none of their functions ever directly modify that referand, but if the contained type were a const pointer, then users wouldn't be able to modify the referand either (without a const cast). It's certainly safer to cast away const before searching, than to cast away const before modifying, if it must be one of the two...

Btw, hasWidget would be shorter if you use count rather than find. It's also marginally const-safer in general (not in this case) to use count, because find with this const_cast returns an iterator that could be used to modify the Widget, whereas count doesn't. So you don't have to worry what happens to the return value of count. Obviously here that return value is entirely under control anyway.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • This is just an abstraction from existing code -- in the actual code-base I do modify the Widgets, hence the key type is desirable. – Marc Nov 30 '10 at 10:56
  • Having discussed this further (above), it looks like I was wrong about the original key type being 'desirable' -- time to refactor! – Marc Nov 30 '10 at 14:35
1

Yes, that's a reasonable use of const_cast<>. You should consider making hasWidget const.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
1

Why not change hasWidget to take a Widget*? The interface is dodgy at the moment, because it implies that you're looking for Widgets by value in the underlying map, when you're actually looking for them by address. The method should also be const, I reckon:

bool hasWidget(Widget *) const;
Stuart Golodetz
  • 20,238
  • 4
  • 51
  • 80
  • Admittedly I was originally unsure whether to require a pointer or reference as the parameter. In the original code, there are a host of public member functions that operate over `Widget` objects (all of which take `const Widget&`); as such, it seemed most sensible (from an _interface_ perspective) to maintain consistency. After all, the client needn't be aware that a `map` is being searched. – Marc Nov 30 '10 at 11:08
  • @Marc: It sounds like the original design might be dodgy as well :) But without the code, it's impossible to say. – Stuart Golodetz Nov 30 '10 at 11:15
  • Maybe so! Could we explore this? As I mentioned in a comment below, the actual class doesn't have the zany constructor and destructor -- they're offered above to provide a self-contained example with a little bit of data, perhaps unwisely (I was wary of posting too much code at the outset). Let's just say that I have a set of member functions, including `addWidget`, `removeWidget`, `hasWidget`, `valueOfWidget` etc. -- and `Widget` s are expected to be managed externally (i.e. they're not destroyed from the class containing the `map` and as such don't have to be created with `new`). – Marc Nov 30 '10 at 12:05
  • @Marc: This seems to clarify things a bit -- I'm not sure the original design is as dodgy as I thought. The idea seems to be to make an easy-to-use lookup table for Widgets, bearing in mind that Widgets themselves can't be sorted -- hence the use of `Widget*` as the key type. If the Widgets are managed externally, I don't see why this shouldn't work fine. And I agree with your use of `const Widget&` for the argument type in the context. Still think `hasWidget` should be a `const` method though, and think Steve Jessop has a point -- why are you modifying Widgets within `Foo`? – Stuart Golodetz Nov 30 '10 at 12:35
  • Since you said that things like `addWidget` take a `const Widget&`, surely they're all having to do a `const_cast` as well? The design doesn't make sense to me unless the key type of the `map` is `const Widget*` rather than `Widget*`. – Stuart Golodetz Nov 30 '10 at 12:37
  • Hmm, right you are. There are one or two member functions in the correlate of `Foo` that manipulate the internal state of a `Widget` (so long as it's a key in `widget_map`); these take `Widget&`. Perhaps I should be using `const_cast` here instead -- adding constness seems far more desirable than stripping it off. – Marc Nov 30 '10 at 13:43
  • So to summarise: it seems folks are suggesting that (i) `const_cast` isn't evil in the sort of case outlined above but (ii) my abstracted example is clearly over-simplified (to the point of confusion). Having said that, I should be using (a) either smart pointers or plain types instead of pointers as keys in the `map` and (b) the keys might as well be marked as `const`. Is that a fair summary? – Marc Nov 30 '10 at 13:46
  • @Marc: Personally, I think it's fine to use pointers as keys in the map in the case you seem to be describing, but they should point to `const` objects to avoid the need to use `const_cast`. What are the member functions of `Foo` that are manipulating the internal state of a `Widget`? They seem to be messing up what would otherwise be a passable design. – Stuart Golodetz Nov 30 '10 at 13:52
  • Yes, you're right -- this is what caused the confusion in the first place. Re-examining the original code, I'm not so sure that the `(Widget&)` member functions mess things up though -- with a bit of refactoring they can simply call the predicates requiring `const Widget&` yielding quite a nice design (and a _big_ improvement). – Marc Nov 30 '10 at 14:05
  • ...in which case, `hasWidget` can be `const`, though the iterator assignment now needs to be: `map::const_iterator it = widget_map.find(&w);` – Marc Nov 30 '10 at 14:15
1

A map with a key where the key is pointer is unwieldy - the only way to look it up is to have the same pointer. For this to work, you have to guarantee that the the hasWidget method will get called with an object that has the same address!

Surely you should implement Widget properly such that it has the correct operators overloaded to act as a key in a std::map! In your map, you can then simply have:

std::map<Widget, int>

And then your find doesn't need a const_cast!

Nim
  • 33,299
  • 2
  • 62
  • 101
  • ...and if copying Widgets is especially costly, which would make this approach unattractive, you're probably better off at least using something like `map,int>`. – Stuart Golodetz Nov 30 '10 at 11:08
  • Interesting; I was under the impression that it was good practice to use pointers in STL containers? Copying cost isn't a major concern in the current code-base -- I just wanted to employ containers sensibly from the outset.... – Marc Nov 30 '10 at 11:16
  • ...agreed, however at that point it's probably worth reconsidering how the mapping is done (may be there is another attribute of `Widget` that can act as *the key*)... – Nim Nov 30 '10 at 11:16
  • 3
    @Marc: no, it isn't "good practice" to use pointers in STL containers, *certainly* not just for the sake of having pointers. Who told you that? However, the key of a map is `const`, so in your case where you want to modify the Widgets, this wouldn't work. – Steve Jessop Nov 30 '10 at 11:19
  • @Marc, I think that depends on the complexity of the object you are storing in the container. If it is trivial (with a trivial copy constructor etc.) then there is no need for the complexity introduced by pointers, if the object is very complex and has an expensive copy constructor, then store a `shared_ptr` as @sgolodetz mentions. – Nim Nov 30 '10 at 11:19
  • ...but as @Steve Jessop says, if you need to modify the key, then you need to reconsider how you map... – Nim Nov 30 '10 at 11:21
  • @Steve-Jessop I suppose I'm referring to Item 3 in Effective STL. Meyers doesn't advocate pointers per se, but he doesn't rule them out either. I guess 'best practice' is incorrect though. Containers of smart pointers, on the other hand, seem to be quite strongly advocated. – Marc Nov 30 '10 at 12:12
  • @Marc: which edition? Item 3 in mine is "use const where possible", not "use pointers where possible" ;-) Containers of smart pointers are often advocated as the next best thing if a container of values is impossible for some reason, although Boost pointer containers are equally in the running for that second-string position. – Steve Jessop Nov 30 '10 at 14:33
  • @Marc: D'oh! Sorry. I was especially confused by the fact that Effective C++ does mention Widgets in Item 3 (in common with half the other items, I realise on reflection...). OK, it's true, he does say that *if* copying is too expensive, *then* pointers are an "easy" way to solve that problem. "Unfortunately, containers of pointers have their own STL-related headaches", and smart pointers help with the headaches. But *if* copying isn't too expensive, who cares about copying, let's avoid the headaches. It also depends what container - in fact `map` never relocates its contents as `vector` does. – Steve Jessop Nov 30 '10 at 14:50
  • Right -- perhaps my code suffers from overzealous "optimisation", introducing headaches where they needn't arise. Even so, this conversation has yielded some fantastic feedback (thanks everybody!). – Marc Nov 30 '10 at 14:58
1

This looks clunky to me. Identifying objects by their physical addresses is quite "special", admittedly it's unique, but it's weird too.

I would strongly consider reversing the map:

std::map<Widget::Id, Widget*>

where Widget::Id could simply be an int or similar.

There would not be any issue with the const-ness then.

To delve deeper, you could also have a look at the Boost Pointer Container library:

boost::ptr_map<Widget::Id, Widget>

which would alleviate the memory management issues.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Thanks Matthieu. @Nim also mentioned using an attribute of `Widget` as the key in place of the `Widget` itself, an interesting proposition. – Marc Nov 30 '10 at 14:41
  • I'm also trying to avoid external dependencies in this simple case (i.e., this project isn't using Boost yet, so I'd like to find the most appealing solution given the current build environment). – Marc Nov 30 '10 at 14:42
  • @Marc: I must admit I consider Boost at the same level of dependencies I consider the STL --> use everywhere. Anyway the simple `map` approach would work too, it's just than you then have to deal with memory yourself. It would be easier if `Widget` had value-semantics and could just be copied. – Matthieu M. Nov 30 '10 at 15:12