Terminology


There is a small C++ vocabulary that every programmer should understand. The following terms are important enough that it is worth making sure we agree on what they mean.

A declaration tells compilers about the name and type of something, but it omits certain details. These are declarations:

 extern int x;                                  // object declaration std::size_t numDigits(int number);             // function declaration class Widget;                                  // class declaration template<typename T>                           // template declaration class GraphNode;                               // (see Item 42 for info on                                                // the use of "typename") 

Note that I refer to the integer x as an "object," even though it's of built-in type. Some people reserve the name "object" for variables of user-defined type, but I'm not one of them. Also note that the function numDigits' return type is std::size_t, i.e., the type size_t in namespace std. That namespace is where virtually everything in C++'s standard library is located. However, because C's standard library (the one from C89, to be precise) can also be used in C++, symbols inherited from C (such as size_t) may exist at global scope, inside std, or both, depending on which headers have been #included. In this book, I assume that C++ headers have been #included, and that's why I refer to std::size_t instead of just size_t. When referring to components of the standard library in prose, I typically omit references to std, relying on you to recognize that things like size_t, vector, and cout are in std. In example code, I always include std, because real code won't compile without it.

size_t, by the way, is just a typedef for some unsigned type that C++ uses when counting things (e.g., the number of characters in a char*-based string, the number of elements in an STL container, etc.). It's also the type taken by the operator[] functions in vector, deque, and string, a convention we'll follow when defining our own operator[] functions in Item 3.

Each function's declaration reveals its signature, i.e., its parameter and return types. A function's signature is the same as its type. In the case of numDigits, the signature is std::size_t (int), i.e., "function taking an int and returning a std::size_t." The official C++ definition of "signature" excludes the function's return type, but in this book, it's more useful to have the return type be considered part of the signature.

A definition provides compilers with the details a declaration omits. For an object, the definition is where compilers set aside memory for the object. For a function or a function template, the definition provides the code body. For a class or a class template, the definition lists the members of the class or template:

 int x;                                           // object definition std::size_t numDigits(int number)                // function definition. {                                                // (This function returns   std::size_t digitsSoFar = 1;                   // the number of digits                                                  // in its parameter.)   while ((number /= 10) != 0) ++digitsSoFar;   return digitsSoFar; } class Widget {                                   // class definition public:   Widget();   ~Widget();   ... }; template<typename T>                             // template definition class GraphNode {             public:   GraphNode();   ~GraphNode();   ... }; 

Initialization is the process of giving an object its first value. For objects of user-defined types, initialization is performed by constructors. A default constructor is one that can be called without any arguments. Such a constructor either has no parameters or has a default value for every parameter:

 class A { public:   A();                                     // default constructor }; class B { public:   explicit B(int x = 0, bool b = true);    // default constructor; see below };                                         // for info on "explicit" class C { public:   explicit C(int x);                       // not a default constructor }; 

The constructors for classes B and C are declared explicit here. That prevents them from being used to perform implicit type conversions, though they may still be used for explicit type conversions:

 void doSomething(B bObject);         // a function taking an object of                                      // type B B bObj1;                             // an object of type B doSomething(bObj1);                  // fine, passes a B to doSomething B bObj2(28);                         // fine, creates a B from the int 28                                      // (the bool defaults to true) doSomething(28);                     // error! doSomething takes a B,                                      // not an int, and there is no                                      // implicit conversion from int to B doSomething(B(28));                  // fine, uses the B constructor to                                      // explicitly convert (i.e., cast) the                                      // int to a B for this call. (See                                      // Item 27 for info on casting.) 

Constructors declared explicit are usually preferable to non-explicit ones, because they prevent compilers from performing unexpected (often unintended) type conversions. Unless I have a good reason for allowing a constructor to be used for implicit type conversions, I declare it explicit. I encourage you to follow the same policy.

Please note how I've highlighted the cast in the example above. Throughout this book, I use such highlighting to call your attention to material that is particularly noteworthy. (I also highlight chapter numbers, but that's just because I think it looks nice.)

The copy constructor is used to initialize an object with a different object of the same type, and the copy assignment operator is used to copy the value from one object to another of the same type:

 class Widget { public:   Widget();                                 // default constructor   Widget(const Widget& rhs);                // copy constructor   Widget& operator=(const Widget& rhs);     // copy assignment operator   ... }; Widget w1;                                  // invoke default constructor Widget w2(w1);                              // invoke copy constructor w1 = w2;                                    // invoke copy                                             // assignment operator 

Read carefully when you see what appears to be an assignment, because the "=" syntax can also be used to call the copy constructor:

 Widget w3 = w2;                           // invoke copy constructor! 

Fortunately, copy construction is easy to distinguish from copy assignment. If a new object is being defined (such as w3 in the statement above), a constructor has to be called; it can't be an assignment. If no new object is being defined (such as in the "w1 = w2" statement above), no constructor can be involved, so it's an assignment.

The copy constructor is a particularly important function, because it defines how an object is passed by value. For example, consider this:

 bool hasAcceptableQuality(Widget w); ... Widget aWidget; if (hasAcceptableQuality(aWidget)) ... 

The parameter w is passed to hasAcceptableQuality by value, so in the call above, aWidget is copied into w. The copying is done by Widget's copy constructor. Pass-by-value means "call the copy constructor." (However, it's generally a bad idea to pass user-defined types by value. Pass-by-reference-to-const is typically a better choice. For details, see Item 20.)

The STL is the Standard Template Library, the part of C++'s standard library devoted to containers (e.g., vector, list, set, map, etc.), iterators (e.g., vector<int>::iterator, set<string>::iterator, etc.), algorithms (e.g., for_each, find, sort, etc.), and related functionality. Much of that related functionality has to do with function objects: objects that act like functions. Such objects come from classes that overload operator(), the function call operator. If you're unfamiliar with the STL, you'll want to have a decent reference available as you read this book, because the STL is too useful for me not to take advantage of it. Once you've used it a little, you'll feel the same way.

Programmers coming to C++ from languages like Java or C# may be surprised at the notion of undefined behavior. For a variety of reasons, the behavior of some constructs in C++ is literally not defined: you can't reliably predict what will happen at runtime. Here are two examples of code with undefined behavior:

 int *p = 0;                            // p is a null pointer std::cout << *p;                       // dereferencing a null pointer                                        // yields undefined behavior char name[] = "Darla";                 // name is an array of size 6 (don't                                        // forget the trailing null!) char c = name[10];                     // referring to an invalid array index                                        // yields undefined behavior 

To emphasize that the results of undefined behavior are not predictable and may be very unpleasant, experienced C++ programmers often say that programs with undefined behavior can erase your hard drive. It's true: a program with undefined behavior could erase your hard drive. But it's not probable. More likely is that the program will behave erratically, sometimes running normally, other times crashing, still other times producing incorrect results. Effective C++ programmers do their best to steer clear of undefined behavior. In this book, I point out a number of places where you need to be on the lookout for it.

Another term that may confuse programmers coming to C++ from another language is interface. Java and the .NET languages offer Interfaces as a language element, but there is no such thing in C++, though Item 31 discusses how to approximate them. When I use the term "interface," I'm generally talking about a function's signature, about the accessible elements of a class (e.g., a class's "public interface," "protected interface," or "private interface"), or about the expressions that must be valid for a template's type parameter (see Item 41). That is, I'm talking about interfaces as a fairly general design idea.

A client is someone or something that uses the code (typically the interfaces) you write. A function's clients, for example, are its users: the parts of the code that call the function (or take its address) as well as the humans who write and maintain such code. The clients of a class or a template are the parts of the software that use the class or template, as well as the programmers who write and maintain that code. When discussing clients, I typically focus on programmers, because programmers can be confused, misled, or annoyed by bad interfaces. The code they write can't be.

You may not be used to thinking about clients, but I'll spend a good deal of time trying to convince you to make their lives as easy as you can. After all, you are a client of the software other people develop. Wouldn't you want those people to make things easy for you? Besides, at some point you'll almost certainly find yourself in the position of being your own client (i.e., using code you wrote), and at that point, you'll be glad you kept client concerns in mind when developing your interfaces.

In this book, I often gloss over the distinction between functions and function templates and between classes and class templates. That's because what's true about one is often true about the other. In situations where this is not the case, I distinguish among classes, functions, and the templates that give rise to classes and functions.

When referring to constructors and destructors in code comments, I sometimes use the abbreviations ctor and dtor.

Naming Conventions

I have tried to select meaningful names for objects, classes, functions, templates, etc., but the meanings behind some of my names may not be immediately apparent. Two of my favorite parameter names, for example, are lhs and rhs. They stand for "left-hand side" and "right-hand side," respectively. I often use them as parameter names for functions implementing binary operators, e.g., operator== and operator*. For example, if a and b are objects representing rational numbers, and if Rational objects can be multiplied via a non-member operator* function (as Item 24 explains is likely to be the case), the expression

 a * b 

is equivalent to the function call

 operator*(a,b) 

In Item 24, I declare operator* like this:

 const Rational operator*(const Rational& lhs, const Rational& rhs); 

As you can see, the left-hand operand, a, is known as lhs inside the function, and the right-hand operand, b, is known as rhs.

For member functions, the left-hand argument is represented by the this pointer, so sometimes I use the parameter name rhs by itself. You may have noticed this in the declarations for some Widget member functions on page 5. Which reminds me. I often use the Widget class in examples. "Widget" doesn't mean anything. It's just a name I sometimes use when I need an example class name. It has nothing to do with widgets in GUI toolkits.

I often name pointers following the rule that a pointer to an object of type T is called pt, "pointer to T." Here are some examples:

 Widget *pw;                           // pw = ptr to Widget class Airplane; Airplane *pa;                         // pa = ptr to Airplane class GameCharacter; GameCharacter *pgc;                   // pgc = ptr to GameCharacter 

I use a similar convention for references: rw might be a reference to a Widget and ra a reference to an Airplane.

I occasionally use the name mf when I'm talking about member functions.

Threading Considerations

As a language, C++ has no notion of threads no notion of concurrency of any kind, in fact. Ditto for C++'s standard library. As far as C++ is concerned, multithreaded programs don't exist.

And yet they do. My focus in this book is on standard, portable C++, but I can't ignore the fact that thread safety is an issue many programmers confront. My approach to dealing with this chasm between standard C++ and reality is to point out places where the C++ constructs I examine are likely to cause problems in a threaded environment. That doesn't make this a book on multithreaded programming with C++. Far from it. Rather, it makes it a book on C++ programming that, while largely limiting itself to single-threaded considerations, acknowledges the existence of multithreading and tries to point out places where thread-aware programmers need to take particular care in evaluating the advice I offer.

If you're unfamiliar with multithreading or have no need to worry about it, you can ignore my threading-related remarks. If you are programming a threaded application or library, however, remember that my comments are little more than a starting point for the issues you'll need to address when using C++.

TR1 and Boost

You'll find references to TR1 and Boost throughout this book. Each has an Item that describes it in some detail (Item 54 for TR1, Item 55 for Boost), but, unfortunately, these Items are at the end of the book. (They're there because it works better that way. Really. I tried them in a number of other places.) If you like, you can turn to those Items and read them now, but if you'd prefer to start the book at the beginning instead of the end, the following executive summary will tide you over:

  • TR1 ("Technical Report 1") is a specification for new functionality being added to C++'s standard library. This functionality takes the form of new class and function templates for things like hash tables, reference-counting smart pointers, regular expressions, and more. All TR1 components are in the namespace tr1 that's nested inside the namespace std.

  • Boost is an organization and a web site (http://boost.org) offering portable, peer-reviewed, open source C++ libraries. Most TR1 functionality is based on work done at Boost, and until compiler vendors include TR1 in their C++ library distributions, the Boost web site is likely to remain the first stop for developers looking for TR1 implementations. Boost offers more than is available in TR1, however, so it's worth knowing about in any case.



Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
ISBN: 321334876
EAN: N/A
Year: 2006
Pages: 102

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net