Previous Table of Contents Next


6.5.4. Some Minor Features

By definition, minor features don’t affect the way we can design a C++ program. They can, however, have a strong impact on the clarity of a code fragment. A minor feature can also help overall code quality by eliminating some error-prone aspect of programming. Thus, the compound effect of minor features on programs and on programmers’ confidence in their code can be significant. Otherwise, why bother with minor features at all? Here I give a few examples. Interestingly enough, programmers seem to be spending far more time discussing minor issues than thinking about and discussing major ones.

6.5.4.1. Explicit Constructors

By default, a constructor taking a single argument defines a conversion from its argument type. Here’s an example:

   class String {
       String(const char*);  // constructor from C-style string;
                             // defines conversion from C-style string
                                to String
       // ...
   };

   void f()
   {
       String s(“explicit construction”);
       s = String(“new string”);
       s = “Implicit conversions can be very convenient”;
       // ...
   }

However, this implicit conversion is not always ideal. For example, the stack from section 6.2.4.8 takes an integer size. Consequently, it also defines conversion from int to stack:

   void g()
   {
       stack s(10);      // stack of 10 elements
       stack s2 = 20;    // stack of 20 elements (surprising?)

       s2 = 30;          // surprise: means “s2 = stack(30);”
                         // assign a stack of 30 elements to s2

       // ...
   };

The obvious solution—allowing the user to specify which constructors define implicit conversions in addition to defining ways of explicitly constructing objects—was adopted as the result of a proposal from Nathan Myers with a syntax proposed by me:

   class Stack {
   public:
       explicit Stack(int); // “explicit” means:
                            // don’t define an implicit conversion

       // ...
   };

   Stack s1(10);            // ok
   Stack s2 = 10;           // error: no implicit conversion from int to
                               Stack

It can be argued that by default, constructors ought not to define implicit conversions and that what’s needed is an implicit or conversion keyword to allow the programmer to indicate that a constructor should define an implicit conversion. However, after 15 years of active use of C++, such arguments are academic.

6.5.4.2. A More Explicit Notation for Type Conversion

In C and badly written C++, explicit type conversion—often called casting—is frequent and a major source of errors. Most explicit type conversions are also inherently low-level and unsafe operations, so it has been my long-term goal to eliminate their use. In well-designed C++ code, explicit type conversions are very rare. For example, The C++ Programming Language, Third Edition (Stroustrup, 1997) uses only five casts in realistic examples.

The traditional, C-style, cast is syntactically unobtrusive and can perform a variety of logically unrelated conversions:

   struct Derived : public Base { /* ... */ };

   void f(int i, double d, char* p, Base* pb, Derived* pd, const char* p)
   {
       i = (int)d;           // truncating arithmetic conversion
       d = (double)i;        // arithmetic conversion
       i = (int)p;           // unsafe, implementation
                             // dependent pointer-to-int conversion
       p = (char*)i;         // unsafe, implementation
                             // dependent int-to-pointer conversion
       pd = (Derived*)pb;    // class hierarchy navigation
       p = (char*)pc;        // remove const protection
   }

Few programmers can consistently explain what such conversions do and whether the semantics of one is well-defined, implementation-defined, or undefined.

After a proposal by me, supported by an analysis of millions of lines of code by Dag Bruck and Sean Corfield, and other evidence of problems related to the use of casts, the standards committee adopted a set of less error-prone conversion operators:

   void g(int i, double d, char* p, Base* pb, Derived* pd, const char* p)
   {
       i = static_cast<int>(d);
       d = static_cast<double>(i);
       i = reinterpret_cast<int>(p);
       p = reinterpret_cast<char*>(i);
       pd = static_cast<Derived*>(pb);    // a runtime checked
                                          // dynamic_cast (section 6.5.8)
                                          // might be preferable
       p = const_cast<char*>(pc);
   }

Each of these operators can perform only a specific subset of all possible conversion operations. This enables some compile-time checking, encourages the programmer to think about what kind of conversion is needed, and gives the reader a clue about the intended meaning. For example, a static_cast can perform only relatively well-behaved conversions; if you need to do a conversion that is so nasty that a conversion back to the original type is required for reasonably safe use, a reinterpret_cast is required. The names of the conversion operators were deliberately chosen to be long and ugly to remind people of the nature of the operations they perform.

Unfortunately, the committee was not able to decide to deprecate C-style casts, so individual programmers and organizations must find ways to avoid them.


Previous Table of Contents Next