Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 16.  Keywords


16.2. temporary

It is generally a bad thing to use implicit conversion operators (see Part Four, and [Meye1996, Sutt2000, Sutt2002]), and a class such as the following will bring howls of derision upon its author these days:



class String


{


public:


  operator char const *() const;


};





String s("Little Nose");


puts(s); // Uses implicit conversion operator



Hence the standard string class does not provide an implicit conversion operator. Instead it provides the c_str() method, which returns a pointer to const char (or wchar_t). Let's change String to correspond to the standard.



class String


{


  . . .


public:


  char const *c_str() const;


};





String s("Little Nose");


puts(s.c_str()); // Uses method - better



It is also generally a bad thing to assign raw pointers to return values. Consider the following potentially dangerous code:



String      s("Little Nose");


char const  *p = s.c_str();


puts(p);



p holds a pointer to the internal buffer of s,[1] but that pointer is valid only so long as s remains unchanged. As soon as s is manipulated in any modifying, or potentially modifying, manner, it may reorganize its internal storage in such a way as to invalidate p.

[1] Actually it holds a pointer to an internal buffer managed by s. A conformant implementation may provide a separate buffer for the c_str() method from its actual internal storage, and implementations that do not zero-terminate their storage will necessarily do so.

This is not a problem with pointers, per se. It's actually an instance of a broader problem whereby client code may be holding onto a reference, pointer, iterator, and so on that becomes invalidated. We look in detail at this problem in Chapter 31, Return Value Lifetime.



String      s("Little Nose");


char const  *p = s.c_str();





s[0] = 'L'; // May cause s to reallocate its storage





puts(p);    // May or may not be valid. Could work; could crash!



Imagine that we had a temporary keyword that would, when applied to a method, require that the values returned by that method could not be assigned to variables. We would decorate the c_str() method with it, as in:



class String


{





  . . .


  temporary char const *c_str() const;


  . . .



Now the compiler will be able to reject the offending statement



String      s("Little Nose");


char const  *p = s.c_str(); // Error – cannot assign result of a temporary method



and the undefined behavior is avoided. Naturally, that wouldn't stop someone from writing something such as the following:



void steal_temp(char const *p, char const **pp)


{


  *pp = p;


}





String      s("Little Nose");


char const  *p;





steal_temp(s.c_str(), &p); // Nasty!



However, if we refine the definition of temporary to have a similar propagation as do const and volatile (known as the cv-qualifiers; C++-98: 3.9.3), the temporary nature of the pointer can be enforced right down to the C run time library.



char *strcpy(char *dest, char const temporary *src);


char *strdup(char const temporary *src);





void steal_temp(char const temporary *s, char const **pp)


{


  *pp = s; // Illegal: implicit cast from temporary pointer


}





void copy_temp(char const temporary *s, char **pp)


{


  *pp = strdup(s); // Ok, since strdup() takes temporary ptr


}





String      s("Little Nose");


char const  *p;


char        *copy;





steal_temp(s.c_str(), &p);    // Will not compile. Good!


copy_temp(s.c_str(), &copy);  // Ok



Since it's only an idea—I've not written a compiler-extension for this yet, as I've been busy writing this book—it might be that applying temporary would stop potentially legitimate uses of assigning such a pointer to a variable, but it would be nice to have the choice; there's always const_cast to give the ultimate power back to the programmer.

Language specifiers (and compiler writers, too) don't like adding new keywords and I know that Beelzebub will be the Olympic ice dancing champion before this idea is adopted, but I think it would be a significant strengthening to C++'s type system and would represent a material increase in code safety. Of course, all the extra typing wouldn't make it (or me) popular.

There's a school of thinking that suggests that returning a pointer from a function is always a no-no, and that this is therefore a nonissue. However, there are plenty of instances where efficiency can overrule such concerns; in any case you must go to the C run time library and/or the operating system C APIs at some point. From a more fundamental point of view, the Shims concept (see Chapter 20) provides a generalizing mechanism that is a highly useful alternative to traits, and shims for certain types do indeed return pointers. Also, certain user-defined cast operators (see section 19.4) may also use a similar mechanism. As the incidence of these "advanced" techniques increases, there may be more impetus to add temporary support to the language. This issue is dealt with in depth in Chapter 31.

Conversely, people from the other side of the C++ debate may suggest that const_cast-ing const, volatile, and const volatile are bad enough and that the prospect of four more permutations involving temporary is ample evidence that C++ has grown too complex and/or that garbage collection should be the only way of doing things.

These two viewpoints are too extreme in my opinion. One suggests that we rely on volition and good practice to restrict ourselves to a subset of possible constructs. The other suggests that the language should have large parts of its flexibility removed. Both are perfectionist points of view, and we're "imperfectionists," are we not? What I would like to see is a middle ground where I still have the power to do as much (or more) as I can now, but with more help from the compiler. That's what it's there for, after all.


      Previous section   Next section