Effective C++ Note 29: Avoid returning "handles" to internal data
What is wrong with the following code?
class String {
public:
String(const char *value); // see Item 11 for pos-
~String(); // sible implementations;
// see Item M5 for comments
// on the first constructor
operator char *() const; // convert String -> char*;
// see also Item M5
...
private:
char *data;
};
const String B("Hello World"); // B is a const object
char *str = B; // calls B.operator char*()
strcpy(str, "Hi Mom"); // modifies what str
// points to
Well, constant object B is modified. Why is that? That is because we returned a handle to the internal member variable data, and we made the member function operator *() const, which means that it can be called on a const object.
A better solution would be like this:
class String {
public:
...
operator const char *() const; // convert String -> char*;
...
};
Another operator of String object usually overloaded is operator[].
class String {
public:
...
char& operator[](int index) const
{ return data[index]; }
private:
char *data;
};
String s = "I'm not constant";
s[0] = 'x'; // fine, s isn't const
const String cs = "I'm constant";
cs[0] = 'x'; // this modifies the const
// string, but compilers
// won't notice
Here, constant Object cs is changed. What do we do, then? Perhaps the best way is to implement two different operator[].
class String {
public:
...
char& operator[](int index)
{ return data[index]; }
const char& operator[](int index) const
{ return data[index]; }
private:
char *data;
};
However, there is still some problems with the code above. What if we do this:
String f1(){return "Hello World";}
const char * str=f1;
const char &ch=f1[0];
cout<<str<<ch;
The result is undefined. Because the lifetime of temporary objects extends only until the end of the expression containing the call to the function. So when we call cout<<str<<ch the objects of which the data they point to have been destroyed. And using a pointer or a reference to unknown memory always lead to undefined behavior.
In a word, though it is rarely possible to eliminate all dangling pointers, it is better that we avoid returning handles to internal data.