Previous | Table of Contents | Next |
By definition, minor features dont 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. Heres 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 solutionallowing the user to specify which constructors define implicit conversions in addition to defining ways of explicitly constructing objectswas adopted as the result of a proposal from Nathan Myers with a syntax proposed by me:
class Stack { public: explicit Stack(int); // explicit means: // dont 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 whats 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 conversionoften called castingis 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 |