Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 12

In this case, the odds and evens parameters are references to int*s. separateOddsAndEvents() can modify the int*s that are used as arguments to the function (through the reference), without any explicit dereferencing. The same logic applies to numOdds and numEvens, which are references to ints.

With this version of the function, you no longer need to pass the addresses of the pointers or ints. The reference parameters handle it for you automatically:

int unSplit[10] = {1, 2, 3, 4, 5, 6, 6, 8, 9, 10}; int *oddNums, *evenNums;

int numOdds, numEvens;

separateOddsAndEvens(unSplit, 10, oddNums, numOdds, evenNums, numEvens);

Keyword Confusion

Two keywords in C++ appear to cause more confusion than any others: const and static. Both of these keywords have several different meanings, and each of their uses presents subtleties that are important to understand.

The const Keyword

The keyword const is short for “constant” and specifies, or requires, that something remain unchanged. As you’ve seen in various places in this book, and probably in real-world code, there are two different, but related, uses of the const keyword: for marking variables and for marking methods. This section provides a definitive discussion of these two meanings.

const Variables

You can use const to “protect” variables by specifying that they cannot be modified. As you learned in Chapters 1 and 7, one important use of this keyword is as a replacement for #define to declare constants. This use of const is its most straightforward application. For example, you could declare the constant PI like this:

const double PI = 3.14159;

You can mark any variable const, including global variables and class data members.

You can also use const to specify that parameters to functions or methods should remain unchanged. You’ve seen examples of this application in Chapters 1 and 9, and other places throughout the book.

const Pointers

When a variable contains one or more levels of indirection via a pointer, applying const becomes trickier. Consider the following lines of code:

int* ip;

ip = new int[10]; ip[4] = 5;

330

Understanding C++ Quirks and Oddities

Suppose that you decide to apply const to ip. Set aside your doubts about the usefulness of doing so for a moment, and consider what it means. Do you want to prevent the ip variable itself from being changed, or do you want to prevent the values to which it points from being changed? That is, do you want to prevent the second line or the third line in the previous example?

In order to prevent the pointed-to value from being modified (as in the third line), you can add the keyword const to the declaration of ip like this:

const int* ip; ip = new int[10];

ip[4] = 5; // DOES NOT COMPILE!

Now you cannot change the values to which ip points.

Alternatively, you can write this:

int const* ip; ip = new int[10];

ip[4] = 5; // DOES NOT COMPILE!

Putting the const before or after the int makes no difference in its functionality.

If you want instead to mark ip itself const (not the values to which it points), you need to write this:

int* const ip = NULL;

ip = new int[10]; // DOES NOT COMPILE! ip[4] = 5;

Now that ip itself cannot be changed, the compiler requires you to initialize it when you declare it.

You can also mark both the pointer and the values to which it points const like this:

int const* const ip = NULL;

An alternative syntax is the following:

const int* const ip = NULL;

Although this syntax might seem confusing, there is actually a very simple rule: the const keyword applies to whatever is directly to its left. Consider this line again:

int const* const ip = NULL;

From left to right, the first const is directly to the right of the word int. Thus, it applies to the int to which ip points. Therefore, it specifies that you cannot change the values to which ip points. The second const is directly to the right of the *. Thus, it applies to the pointer to the int, which is the ip variable. Therefore, it specifies that you cannot change ip (the pointer) itself.

331

Chapter 12

const applies to the level of indirection directly to its left.

The reason this rule becomes confusing is an exception: the first const can go before the variable like this:

const int* const ip = NULL;

This “exceptional” syntax is used much more commonly than the other syntax.

You can extend this rule to any number of levels of indirection. For example:

const int * const * const * const ip = NULL;

const References

const applied to references is usually simpler than const applied to pointers for two reasons. First, references are const by default, in that you can’t change to what they refer. So, C++ does not allow you to mark a reference variable explicitly const. Second, there is usually only one level of indirection with references. As explained earlier, you can’t create a reference to a reference. The only way to get multiple levels of indirection is to create a reference to a pointer.

Thus, when C++ programmers refer to a “const reference,” they mean something like this:

int z;

const int& zRef = z;

zRef = 4; // DOES NOT COMPILE

By applying const to the int, you prevent assignment to zRef, as shown. Remember that const int& zRef is equivalent to int const& zRef. Note, however, that marking zRef const has no effect on z. You can still modify the value of z by changing it directly instead of through the reference

const references are used most commonly as parameters, where they are quite useful. If you want to pass something by reference for efficiency, but don’t want it to be modifiable, make it a const reference. For example:

void doSomething(const BigClass& arg)

{

// Implementation here

}

Your default choice for passing objects as parameters should be const reference. Only if you explicitly need to change the object should you omit the const.

332

Understanding C++ Quirks and Oddities

const Methods

As you read in Chapter 9, you can mark a class method const. That specification prevents the method from modifying any non-mutable data members of the class. Consult Chapter 9 for an example.

The static Keyword

Although there are several uses of the keyword const in C++, all the uses are related and make sense if you think of const as meaning “unchanged.” static is a different story: there are three uses of the keyword in C++, all seemingly unrelated.

Static Data Members and Methods

As you read in Chapter 9, you can declare static data members and methods of classes. static data members, unlike non-static data members, are not part of each object. Instead, there is only one copy of the data member, which exists outside any objects of that class.

static methods are similarly at the class level instead of the object level. A static method does not execute in the context of a specific object.

Chapter 9 provides examples of both static members and methods.

Static Linkage

Before covering the use of the static keyword for linkage, you need to understand the concept of linkage in C++. As you learned in Chapter 1, C++ source files are each compiled independently, and the resulting object files are linked together. Each name in a C++ source file, including functions and global variables, has a linkage that is either internal or external. External linkage means that the name is available from other source files. Internal linkage (also called static linkage) means that it is not. By default, functions and global variables have external linkage. However, you can specify internal (or static) linkage by prefixing the declaration with the keyword static. For example, suppose you have two source files: FirstFile.cpp and AnotherFile.cpp. Here is FirstFile.cpp:

// FirstFile.cpp

void f();

int main(int argc, char** argv)

{

f(); return (0);

}

Note that this file provides a prototype for f(), but doesn’t show the definition.

333

Chapter 12

Here is AnotherFile.cpp:

// AnotherFile.cpp

#include <iostream> using namespace std;

void f();

void f()

{

cout << “f\n”;

}

This file provides both a prototype and a definition for f(). Note that it is legal to write prototypes for the same function in two different files. That’s precisely what the preprocessor does for you if you put the prototype in a header file that you #include in each of the source files. The reason to use header files is that it’s easier to maintain (and keep synchronized) one copy of the prototype. However, for this example we don’t use a header file.

Each of these source files compiles without error, and the program links fine: because f() has external linkage, main() can call it from a different file.

However, suppose you apply static to f() in AnotherFile.cpp:

// AnotherFile.cpp #include <iostream> using namespace std;

static void f();

void f()

{

cout << “f\n”;

}

Now each of the source files compiles without error, but the linker step fails because f() has internal (static) linkage, making it unavailable from FirstFile.cpp. Some compilers issue a warning when static methods are defined but not used in that source file (implying that they shouldn’t be static, because they’re probably used elsewhere).

Note that you don’t need to repeat the static keyword in front of the definition of f(). As long as it precedes the first instance of the function name, there is no need to repeat it.

Now that you’ve learned all about this use of static, you will be happy to know that the C++ committee finally realized that static was too overloaded, and deprecated this particular use of the keyword. That means that it continues to be part of the standard for now, but is not guaranteed to be in the future. However, much legacy C++ code still uses static in this way.

334

Understanding C++ Quirks and Oddities

The supported alternative is to employ anonymous namespaces to achieve the same affect. Instead of marking a variable or function static, wrap it in an unnamed namespace like this:

// AnotherFile.cpp #include <iostream> using namespace std;

namespace {

void f();

void f()

{

cout << “f\n”;

}

}

Entities in an anonymous namespace can be accessed anywhere following their declaration in the same source file, but cannot be accessed from other source files. These semantics are the same as those obtained with the static keyword.

The extern Keyword

A related keyword, extern, seems like it should be the opposite of static, specifying external linkage for the names it precedes. It can be used that way in certain cases. For example, consts and typedefs have internal linkage by default. You can use extern to give them external linkage.

However, extern has some complications. When you specify a name as extern, the compiler treats it as a declaration, not a definition. For variables, this means the compiler doesn’t allocate space for the variable. You must provide a separate definition line for the variable without the extern keyword. For example:

// AnotherFile.cpp extern int x;

int x = 3;

Alternatively, you can initialize x in the extern line, which then serves as the declaration and definition:

// AnotherFile.cpp extern int x = 3;

The extern in this file is not very useful, because x has external linkage by default anyway. The real use of extern is when you want to use x from another source file:

// FirstFile.cpp #include <iostream> using namespace std;

extern int x;

int main(int argc, char** argv)

{

cout << x << endl;

}

335