
According
to Hoyle...
C++0x
Part 3: Making Coding Easier
macCompanion
December 2009
by
Jonathan Hoyle
jonhoyle@mac.com
http://www.jonhoyle.com
Two months ago, we
began our series on the changes that will be coming the C++
language. Last
month we examined some of the updates which were expected to take
place: fixing embarrassments and syncing up with ANSI C99
changes. With this month, we look at some more improvements
available to C++ developers of the future. Specifically, changes
which make coding easier.
Standard C++ Library Enhancements
The Standard C++ Library, including STL (the Standard Template
Library), is a generous supply of useful containers and utilities.
Despite its fullness of capabilities, there were still a number of
components missing. C++0x fills these gaps:
regex: A
long awaited regular expressions class
array<>: A
one-dimensional array containing its size (can be 0)
STL
hash classes: unordered_set<>, unordered_map<>,
etc.
tuple<>: A
templated tuple class, eg: tuple<int,void *,double>
Mac users are fortunate in that they do not have to wait for the
Standard Library changes: they are available today in gcc 4 (the
compiler inside Xcode 2.x and later). These library additions are
within the library namespace std::tr1:: ("tr1" stands for Technical
Report #1, the standard's committee report defining these new classes).
Variadic Templates
For years, the C
language has allowed functions to have a variable number of
parameters. Unfortunately, this was not true of template
arguments within C++98. In C++0x, templates can have a variable
number of types. Here is an example in which a templated
DebugMessage() function
can take advantage of variadic templates:
// Prints to
stderr only when DEBUG flag set
template <typename... TypeArgs>
void
DebugMessage(TypeArgs... args)
{
#ifdef DEBUG
...
// Implement writing to stderr
#else
// Do nothing
#endif
}
// Later in
code
DebugMessage("The value of
n = ", n)
DebugMessage("x = ", x, ", y = ", y, ", z = ", z)
DebugMessage("TRACE:",
" time = ", clock(),
", filename =
", __FILE__,
", line
number = ", LINE__,
", inside
function = ", __func__);
This new flexibility
with macros will make it easier for developers wishing to use them, as
their current restrictions make them occasionally cumbersome.
Delegating Constructors
Other languages, such
as C#, allow one class constructor to invoke another. In C++98,
this was not possible, thus requiring the class designer to create a
separate initialization function if it wished to use common code across
multiple constructors. In C++0x, this becomes available, as the
following example shows:
class X
{
public:
X();
// default constructor
X(void *ptr); // takes a
pointer
X(int value); // takes an
int
};
X::X(): X(NULL) // calls
X(void *)
{
... // other code
}
X::X(void *ptr): X(0) // calls
X(int)
{
... // other code
}
X::X(int value) // does
not delegate
{
... // other code
}
With constructor delegation, C++ class designers can simplify their
implementations.
NULL Pointers
In ANSI C, NULL is
defined as (void *) 0.
In C++, the use of NULL is
deprecated. Why? Because unlike in C, it is illegal in C++
to directly assign a void pointer
to any other type of pointer (without a cast):
void *vPtr = NULL;
// legal C, legal C++
int *iPtr = NULL; //
legal C, illegal C++
//
Cannot assign void * to int * in C++
int *iPtr = 0;
// legal C++
However, the proliferation of NULL in
C++ code remains so great, many compilers simply generate a warning,
not an error, when such a pointer assignment mismatch takes
place. Others redefine NULL in
C++ as simply 0.
Despite these occasional compiler courtesies, it is still very
confusing for beginning C++ programmers, especially in examples such as
these:
void foo(int);
// takes an int
void foo(char *);
// takes a char ptr
foo(0);
// Is this "0" to be a ptr or a
number?
foo(NULL);
// No matching prototype (no void *)
For this reason, C++0x introduces nullptr,
a type-safe nil pointer which can be used with any pointer, but is not
compatible with any integral type:
char *cPtr1 = nullptr;
// a null C++ pointer
char *cPtr2 = 0;
// legal, but deprecated
int n = nullptr;
// illegal
X *xPtr = nullptr;
// can be used with any ptr type
void foo(int);
// takes an int
void foo(char *);
// takes a char ptr
foo(0);
// calls foo(int)
foo(nullptr);
// calls foo(char *)
Essentially, C++ programmers now have a type-safe replacement for NULL,
which matches similar behavior in other more recent languages.
The Amazing Return of auto
When the C language
first evolved, the auto keyword was used to specify to the compiler
that a variable was being allocated on the stack, for example:
auto
x;
/* implicitly an int, placed on stack */
Then ANSI C was
ratified in 1989, the implicit int rule was removed:
auto
x;
/* now illegal in ANSI C */
int
x;
/* OK, auto assumed */
auto
int x;
/* OK, but redundant */
Since that time, auto remained
a keyword in the C (and later C++) languages, even though virtually no
one had used it since the 1970's. After over 30 years of disuse,
the C++0x standard will reintroduce the auto keyword
to mean that the variable type is implied by the initializer:
auto
x = 10;
// x is an int
auto
y = 10.0; // y
is an double
auto
z = 10LL; // z
is a long long
const auto *p = &y;
// p is a const double *
The savings becomes
more significant with complicated types, such as the following example:
void *foo(const int doubleArray[64][16]);
auto
myFcnPtr = foo;
// myFcnPtr is of type "void *(const
int(*)[16])"
In addition, auto becomes
useful for temporary variables whose types aren't important but merely
just have to match. Consider the following function which walks
through an STL container:
void foo(vector<MySpace::MyClass
*> x)
{
for (auto ptr = x.begin();
ptr != x.end(); ptr++)
{
... // Code
modifying the data
}
}
Without auto, the type for variable ptr would be vector<MySpace::MyClass
*>::iterator. Moreover, any change to
this container, such as changing it from a vector<> to a list<>, or changing the class
name or namespace, would require changes in the ptr variable definition, despite the fact its type is
completely unnecessary to note (other than for the compiler).
Note that an initializer
is still required to use auto for C++0x:
auto x; // still illegal in C++0x
But suppose you knew what
type you wanted (based upon another variable) but did not want to
initialize? The new decltype keyword is available for just such
purposes, as the following example shows:
bool SelectionSort(double data[256], double tolerance);
bool BubbleSort(double data[256], double tolerance);
bool QuikSort(double data[256], double tolerance);
decltype(SelectionSort) mySortFcn;
if (bUseSelectionSort) mySortFcn = SelectionSort;
else if (bUseBubbleSort) mySortFcn =
BubbleSort;
else mySortFcn = QuikSort;
With the inclusion of auto
& decltype in C++0x, a great deal of
simplicity is restored to C++ development, without any loss of power.
Coming Up:
C++0x
Part 4: Smart Pointers
C++0x
Part 5: Rvalue References
C++0x
Part 6: Final Thoughts
http://www.maccompanion.com/macc/archives/December2009/Columns/AccordingtoHoyle.htm