I have a class with an std::map of pointers as a member. Now, I'd like to expose that member in a read only fashion: modification is not allowed for neither the map, nor the objects pointed to. Internally I need those pointers to be non-const, and I want to expose them as const.
I do have a solution that compiles at least, but I'd like to know if there's any hidden problems I'll run into with this.
class A
{
public:
const std::map<int, const float*>& GetMap() const { return *(reinterpret_cast< const std::map<int, const float*>* >( &m_Map)); }
private:
st开发者_如何学运维d::map<int, float*> m_Map;
};
There's a possible problem I can think of: if the internal layout of std::map is different for maps of pointers and maps of const pointers, then this will cause ugly bugs. But I cannot think of any sane reason why that would be the case. Anybody has any idea?
To clarify: I am aware that this is a hack, and there are safer solutions (like separate accessor functions). I am just wondering if this would break right away because of some piece of information I'm missing.
This is of course undefined (EDIT: it looks like it's actually only unspecified) behavior because the two maps are (from the language point of view) totally unrelated types. It may appear to work now but sometime it's going to break and cause a ton of headaches.
Did you consider that instead of exposing an implementation detail (that you're using map internally) you could provide const_iterator
s and a find
method for your class's public interface instead?
EDIT: See 5.2.10/7:
A pointer to an object can be explicitly converted to a pointer to an object of different type. 65) Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.
From that quote we conclude that casting from the map with non-const value type to the map with const value type has unspecified behavior. Further, actually dereferencing the converted pointer would probably violate the strict aliasing rules and result in undefined behavior.
You could hold it as a map<int, const float *> and const_cast internally when you need to. This is ugly but legal (as long as you know that all the values pointed to really aren't const).
At least it doesn't involve Undefined Behaviour which I'm pretty certain your solution does. Although as you say it will probably work most of the time on most platforms.
That reinterpret_cast generates a reference with unspecified behavior. Don't do that! Use const_iterators.
class A {
public:
typedef std::map<int, float*> MapType;
typedef MapType::const_iterator const_iterator;
const_iterator begin () const { return m_Map.begin(); }
const_iterator end () const { return m_Map.end(); }
private:
std::map<int, float*> m_Map;
};
void some_function () {
A my_map;
// Code to build the map elided
for (A::const_iterator iter = my_map.begin(); iter < my_map.end(); ++iter) {
do_something_with_but_not_to (*iter);
}
Note that you can also export things such as find that return a const_iterator.
One good reason why it might cause trouble: Even if binary implementation is usually the same (and it usually is, but who knows), then types are still different. Some containers might use some static (or TLS now in C++11) fields (eg. for optimizations/debugging purposes), and those must be different for different types.
Imagine, that such a field would be a (null-initialized) pointer, that is given some significant value in constructor (if not assigned already). As long as no object of this type are constructed it's safe to assume that nobody will deference it, and after first constructor call, it's OK to deference it without checking if it's non-null. Your code can produce container that was never constructed, but it's methods deference internal pointer, leading to difficult to track segfault.
精彩评论