DekGenius.com
Team LiB   Previous Section   Next Section

6.1 Class Definitions

A 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:

Forward class declarations

If two classes refer to each other, you can declare one as an incomplete class, then provide the full definitions of both:

class graphics_context;
class bitmap {
...
  void draw(graphics_context*);
...
};
class graphics_context {
...
  bitblt(const bitmap&);
...
};
Opaque types

Sometimes a class uses hidden helper classes. The primary class can hold a pointer to an incomplete helper class, and you do not need to publish the definition of the helper class. Instead, the definition might be hidden in a library. This is sometimes called a pimpl (for a number of reasons, one of which is "pointer to implementation"). For example:

class bigint {
public:
  bigint(  );
  ~bigint(  );
  bigint(const bigint&);
  bigint& operator=(const bigint&);
  ...
private:
  class bigint_impl;
  std::auto_ptr<bigint_impl> pImpl_;
};

For a complete discussion of the pimpl idiom, see More Exceptional C++, by Herb Sutter (Addison-Wesley).

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;
};

In C, a struct or union name is not automatically a type name, as it is in C++. A variable declaration in C, for example, would need to be elaborated as struct demo x instead of simply demo x. There is no harm in using the fully elaborated form in C++, and this is often done in headers that are meant to be portable between C and C++.

There are times, even in C++, when the fully elaborated form is needed, such as when a function or object declaration hides a class name. In this case, use the elaborated form to refer to the class name. For example, the POSIX API has a structure named stat, which is hidden by a function stat:

extern "C" int stat(const char* filename, struct stat* st);
int main(int argc, char* argv[])
{
  struct stat st;
  if (argc > 1 && stat(argv[1], &st) == 0) show_stats(st);
}

See Chapter 2 for more information about elaborated type specifiers.

6.1.1 Plain Old Data

Some 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:

  • User-defined constructors

  • User-defined destructor

  • User-defined copy assignment operator

  • Virtual functions

  • Base classes

  • Private or protected nonstatic members

  • Nonstatic data members that are references

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 types
struct 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 Classes

A trivial class is another form of restricted class (or union). It cannot have any of the following:

  • User-defined constructors

  • User-defined destructor

  • User-defined copy assignment operator

  • Virtual functions

  • Virtual base classes

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 Unions

A union is like a struct, but with the following restrictions:

  • It cannot have base classes.

  • It cannot be a base class.

  • It cannot have virtual functions.

  • It cannot have static data members.

  • Its data members cannot be references.

  • All of its data members must be trivial.

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 union
struct 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;
}

Unions are used less often in C++ than in C. Two common uses for unions in C have been supplanted with other alternatives in C++:

  • Unions can be used to peek inside the data representation for certain types, but a reinterpret_cast<> can do the same thing.

  • Unions were sometimes used to save memory when storing different kinds of data in a structure, but inheritance is safer and offers more flexibility.

6.1.4 Local Classes

You 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:

  • A local class cannot have static data members.

  • Member functions must be defined inline in the class definition.

  • You cannot refer to nonstatic objects from within a local class, but you can refer to local static objects, local enumerators, and functions that are declared locally.

  • A local class cannot be used as a template argument, so you cannot use a local functor with the standard algorithms.

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(  ));
}
    Team LiB   Previous Section   Next Section