6.1 Class DefinitionsA class definition starts with the class, struct, or union keyword. The difference between a class and a struct is the default access level. (See Section 6.5 later in this chapter for details.) A union is like a struct for which the storage of all the data members overlap, so you can use only one data member at a time. (See Section 6.1.3 later in this section for details.) A class definition can list any number of base classes, some or all of which might be virtual. (See Section 6.4 later in this chapter for information about base classes and virtual base classes.) In the class definition are declarations for data members (called instance variables or fields in some other languages), member functions (sometimes called methods), and nested types. A class definition defines a scope, and the class members are declared in the class scope. The class name itself is added to the class scope, so a class cannot have any members, nested types, or enumerators that have the same name as the class. As with any other declaration, the class name is also added to the scope in which the class is declared. You can declare a name as a class, struct, or union without providing its full definition. This incomplete class declaration lets you use the class name in pointers and references but not in any context in which the full definition is needed. You need a complete class definition when declaring a nonpointer or nonreference object, when using members of the class, and so on. An incomplete class declaration has a number of uses:
Example 6-1 shows several different class definitions. Example 6-1. Class definitions#include <string> struct point { double x, y; }; class shape { public: shape( ); virtual ~shape( ); virtual void draw( ); }; class circle : public shape { public: circle(const point& center, double radius); point center( ) const; double radius( ) const; void move_to(const point& new_center); void resize(double new_radius); virtual void draw( ); private: point center_; double radius_; }; class integer { public: typedef int value_type; int value( ) const; void set_value(int value); std::string to_string( ) const; private: int value_; }; class real { public: typedef double value_type; double value( ) const; void set_value(double value); std::string to_string( ) const; private: double value_; }; union number { number(int value); number(double value); integer i; real r; };
6.1.1 Plain Old DataSome programming languages differentiate between records and classes. Typically, a record is a simple storage container that lacks the more complex features of a class (inheritance, virtual functions, etc.). In C++, classes serve both purposes, but you can do things with simple classes (called POD, for plain old data) that you cannot do with complicated classes. Basically, a POD class is a structure that is compatible with C. More precisely, a POD class or union does not have any of the following:
Also, all nonstatic data members must have POD type. A POD type is a fundamental type, an enumerated type, a POD class or union, or a pointer to or array of POD types Unlike C structures, a POD class can have static data members, nonvirtual functions, and nested types (members that do not affect the data layout in an object). POD classes are often used when compatibility with C is required. In that case, you should avoid using any access specifier labels because they can alter the layout of data members within an object. (A POD class cannot have private or protected members, but you can have multiple public: access specifier labels and still have a class that meets the standard definition of a POD class.) Example 6-2 shows POD types (point1 and info) and non-POD types (point2 and employee). Example 6-2. Declaring POD and non-POD typesstruct point1 { // POD int x, y; }; class point2 { // Not POD public: point2(int x, int y); private: int x, y; }; struct info { // POD static const int max_size = 50; char name[max_size]; bool is_name_valid( ) const; bool operator<(const info& i); // Compare names. }; struct employee : info { // Not POD int salary; }; The virtue of a POD object is that it is just a contiguous area of storage that stores some value or values. Thus, it can be copied byte for byte and retain its value. In particular, a POD object can be safely copied to an array of char or unsigned char, and when copied back, it retains its original value. A POD object can also be copied by calling memcpy; the copy has the same value as the original. A local POD object without an initializer is left uninitialized, but a non-POD object is initialized by calling its default constructor. Similarly, a POD type in a new expression is uninitialized, but a new non-POD object is initialized by calling its default constructor. If you supply an empty initializer to a new expression or other expression that constructs a POD object, the POD object is initialized to 0. A POD class can contain padding between data members, but no padding appears before the first member. Therefore, a pointer to the POD object can be converted (with reinterpret_cast<>) into a pointer to the first element. A goto statement can safely branch into a block, skipping over declarations of uninitialized POD objects. A goto that skips any other declaration in the block results in undefined behavior. (See Chapter 2 for more information about initializing POD objects, and Chapter 4 for more information about the goto statement.) 6.1.2 Trivial ClassesA trivial class is another form of restricted class (or union). It cannot have any of the following:
Also, all base classes must be trivial, and all nonstatic data members must be trivial or have non-class type. Unlike POD classes, a trivial class can have base classes, private and protected members, and members with reference type. Trivial classes are important only because members of a union must be trivial. Fundamental types are trivial, as are pointers to and arrays of trivial types. 6.1.3 UnionsA union is like a struct, but with the following restrictions:
An object of union type has enough memory to store the largest member, and all data members share that memory. In other words, a union can have a value in only one data member at a time. It is your responsibility to keep track of which data member is "active." A union can be declared without a name (an anonymous union), in which case it must have only nonstatic data members (no member functions, no nested types). The members of an anonymous union are effectively added to the scope in which the union is declared. That scope must not declare any identifiers with the same names as the union's members. In this way, an anonymous union reduces the nesting that is needed to get to the union members, as shown in Example 6-3. Example 6-3. An anonymous unionstruct node { enum kind { integer, real, string } kind; union { int intval; double realval; *char strval[8]; }; }; node* makeint(int i) { node* rtn = new node; rtn->kind = node::integer; rtn->intval = i; return rtn; }
6.1.4 Local ClassesYou can declare a local class, that is, a class definition that is local to a block in a function body. A local class has several restrictions when compared to nonlocal classes:
Local classes are not used often. Example 6-4 shows one use of a local class. Example 6-4. A local class// Take a string and break it up into tokens, storing the tokens in a vector. void get_tokens(std::vector<std::string>& tokens, const std::string& str) { class tokenizer { public: tokenizer(const std::string& str) : in_(str) {} bool next( ) { return in_ >> token_; } std::string token( ) const { return token_; } private: std::istringstream in_; std::string token_; }; tokens.clear( ); tokenizer t(str); while (t.next( )) tokens.push_back(t.token( )); } |