DekGenius.com
Team LiB   Previous Section   Next Section

6.3 Member Functions

Member functions implement the behavior of a class. Member functions can be defined within the class definition or separately. You can use the inline function specifier and either the static or virtual (but not both) specifier. (See Chapter 2 for more about function specifiers.) Defining a member function within the class definition declares the function inline, even if you do not use the inline specifier.

A nonstatic member function can have const, volatile, or both function qualifiers. Qualifiers appear after the function parameters and before the exception specification. Function qualifiers are discussed in the next section, Section 6.3.2.

Example 6-8 shows various member function declarations and definitions.

Example 6-8. Declaring and defining member functions
#include <cmath>
#include <iostream>
#include <istream>
#include <ostream>

class point {
public:
  typedef double value_type;
  // Constructors are special member functions.
  explicit point(value_type x = 0.0, value_type y = 0.0);
  value_type x(  )       const { return x_; }
  value_type y(  )       const { return y_; }
  void x(value_type x)         { x_ = x; }
  void y(value_type y)         { y_ = y; }

  value_type distance(  ) const;
  bool operator==(const point& pt) const;

  inline static point origin(  );
private:
  value_type x_, y_;
};

point::point(value_type x, value_type y)
: x_(x), y_(y)
{}

point::value_type point::distance(  )
const
{
  return std::sqrt(x() * x() + y(  ) * y(  ));
}

bool point::operator==(const point& pt)
const
{
  return x() == pt.x() && y(  ) == pt.y(  );
}

inline point point::origin(  )
{
  return point(  );
}

int main(  )
{
  point p1;
  point::value_type n;
  std::cin >> n;
  p1.x(n);
  std::cin >> n;
  p1.y(n);
  if (p1 == point::origin(  ))
    std::cout << "Origin\n";
  else
    std::cout << p1.distance(  ) << '\n';
}

When defining a function inside a class definition, the entire function definition is in the scope of the class. Thus, name lookup for the return type and all parameter types looks first in the class, then in all base classes, and then in the surrounding namespaces. (See Chapter 2 for more information about name lookup.)

If the function definition is outside the class definition, the function return type is at namespace scope. Thus, if the type is a nested type in the class, you must fully qualify the type name. The function name must also be qualified so the compiler knows which class contains the function. After the compiler has seen the class name that qualifies the function name, it enters the class scope. Parameter types are then looked up in the class scope.

6.3.1 Pointers-to-Members

The address of a static member is no different than the address of an ordinary function or object at namespace scope. The address of a nonstatic member that is taken in a member function with an unqualified name is also an ordinary address. The address of a nonstatic member taken with a qualified member name (e.g., &cls::mem), however, is quite different from any other kind of address. The address even has a special name: pointer-to-member. You cannot use reinterpret_cast<> to cast a pointer-to-member to or from an ordinary data pointer or function pointer.

A pointer-to-member does not necessarily point to a particular function or object. Instead, think of it as a handle that keeps track of which function or data member you have selected but does not refer to a specific object. To use a pointer-to-member, you must use the .* or ->* operator, which requires an object or pointer as the lefthand operand and the pointer-to-member as the righthand operand. The operator looks up the member in the object and then obtains the actual data member or member function. (See Chapter 3 for more information about pointer-to-member expressions.) Example 6-9 shows one use of a pointer-to-member.

Example 6-9. A pointer-to-member
#include <iostream>
#include <ostream>

class base {
public:
  base(int i) : x_(i) {}
  virtual ~base(  ) {}
  virtual void func(  ) { std::cout << "base::func(  )\n"; }
private:
  int x_;
};

class derived : public base {
public:
  derived(int i) : base(i) {}
  virtual void func(  ) { std::cout << "derived::func(  )\n"; }
};

int main(  )
{
  base *b = new derived(42);
  void (base::*fp)(  ) = &base::func;
  (b->*fp)(  );                      // Prints derived::func(  )
}

6.3.2 this Pointer

A nonstatic member function can be called only for an object of its class or for a derived class. The object is implicitly passed as a hidden parameter to the function, and the function can refer to the object by using the this keyword, which represents an rvalue pointer to the object. That is, if the object has type T, this is an rvalue of static type T*. In a call to a virtual function, the object's dynamic type might not match the static type of this.

Static member functions do not have this pointers. (See the next section, Section 6.3.3.)

If the function is qualified with const or volatile, the same qualifiers apply to this within the member function. In other words, within a const member function of class T, this has type const T*. A const function, therefore, cannot modify its nonstatic data members (except those declared with the mutable specifier).

Within a member function, you can refer to data members and member functions using just their names, and the compiler looks up the unqualified names in the class, in its ancestor classes, and in namespace scopes. (See Chapter 2 for details.) You can force the compiler to look only in the class and ancestor classes by explicitly using this to refer to the members—for example, this->data and this->func( ). (When using templates, an explicit member reference can reduce name lookup problems; see Chapter 7 for details.)

Example 6-10 shows some typical uses of this.

Example 6-10. The this keyword
class point {
public:
  point(int x=0, int y=0) : x_(x), y_(y) {}
  int x(  ) const { return this->x_; }
  int y(  ) const { return y_; }
  void x(int x) { this->x_ = x; }
  void y(int y) { y_ = y; }
  bool operator==(const point& that) {
    return this->x(  ) == that.x(  ) &&
           this->y(  ) == that.y(  );
  }
  void move_to(context*) const { context->move_to(*this); }
  void draw_to(context*) const { context->draw_to(*this); }
  void get_position(context*)  { context->getpos(this); }
private:
  int x_, y_;
};

6.3.3 Static Member Functions

A static member function is like a function declared at namespace scope; the class name assumes the role of the namespace name. Within the scope of the class, you can call the function using an unqualified name. Outside the class scope, you must qualify the function name with the class name (e.g., cls::member) or by calling the function as a named member of an object (e.g., obj.member). In the latter case, the object is not passed as an implicit parameter (as it would be to a nonstatic member function), but serves only to identify the class scope in which the function name is looked up.

Static member functions have the following restrictions:

  • They do not have this pointers.

  • They cannot be virtual.

  • They cannot have const or volatile qualifiers.

  • They cannot refer to nonstatic members, except as members of a specific object (using the . or -> operator).

  • A class cannot have a static and nonstatic member function with the same name and parameters.

Example 6-11 shows some uses of static member functions.

Example 6-11. Static member functions
class point {
public:
  point(int x, int y);

  static point origin(  ) { return point(0, 0); }

  // Error: calls non-static function, x(  )
  static bool is_zero(  ) { return x(  ) == 0; }

  int x(  ) const;
  int y(  ) const;
private:
  int x_, y_;
};

6.3.4 Constructors

Constructors and destructors are special forms of member functions. A constructor is used to initialize an object, and a destructor is used to finalize an object.

6.3.4.1 Declaring constructors

A constructor's name is the same as the class name. If you use typedef to create a synonym for the class name, you cannot use the typedef name as the constructor name:

struct point {
  point(int x, int y);
...
typedef point p;

p::point(int x, int y)     {  . . .  } // OK
point::point(int x, int y) {  . . .  } // OK
p::p(int x, int y)         {  . . .  } // Error
point::p(int x, int y)     {  . . .  } // Error

A constructor cannot have a return type, and you cannot return a value. That is, you must use a plain return; or return an expression of type void. A constructor cannot have const or volatile qualifiers, and it cannot be virtual or static.

Constructors can initialize data members with a list of member initializers, which appear after the function header but before the function body. A colon separates the header from the comma-separated initializers. Each initializer names a data member, an immediate base class, or a virtual base class. The value of the initializer follows in parentheses:

class-name(parameters)
: member(expr), base-class(expr-list), ...
compound-statement

You can also use a function try block, which wraps the constructor initializers as part of the try block. (See Chapter 4 for more information about try blocks and exception handlers.) The syntax is:

class-name(parameters)
try
  : member(expr), base-class(expr-list), ...
  compound-statement
exception-handlers

You can initialize a base class by invoking any of its constructors, passing zero or more expressions as arguments. See Section 6.4 later in this chapter for more information.

The order in which initializers appear in the initializer list is irrelevant. The order of initialization is determined by their declarations according to the following rules:

  • Virtual base classes are initialized first, in order of declaration in a depth-first traversal of the inheritance graph. Each virtual base class is initialized exactly once for the most-derived object.

  • Nonvirtual, direct base classes are initialized next, in order of declaration.

  • Nonstatic data members are initialized next, in order of declaration.

  • Finally, the body of the constructor is executed.

Note that each base class is constructed according to the same rules. This causes the root of the inheritance tree to be initialized first, and the most-derived class to be initialized last.

A constructor can be declared with the explicit specifier. An explicit constructor is never called for an implicit type conversion, but only for function-like initialization in a declaration and for explicit type casts. See the next section for more information.

6.3.4.2 Special constructors

Two kinds of constructors are special, so special they have their own names: default constructors and copy constructors.

A default constructor can be called with no arguments. It might be declared with no parameters, default arguments for every parameter, or an ellipsis as the sole parameter. (The last case is uncommon.) Default constructors are called when you construct an array of objects, when you omit the initializer for a scalar object, or when you use an empty initializer in an expression:

point corners[2];
point p;
point *ptr = new point(  );

(Remember not to use an empty initializer in a declaration. For example, point p( ); declares a function named p, not a default-initialized point object. See Chapter 2 for details.)

A copy constructor can take a single argument whose type is a reference to the class type. (Additional parameters, if any, must have default arguments.) The reference is usually const, but it does not have to be. The copy constructor is called to copy objects of the class type when passing objects to functions and when returning objects from functions.

The following example shows a default and a copy constructor:

struct point {
  point(  );                // Default constructor
  point(const point& pt);   // Copy constructor
  ...

Default and copy constructors are so important, the compiler might generate them for you. See Section 6.3.6 later in this chapter for details.

6.3.4.3 Calling constructors

Constructors are special because you never call them directly. Instead, the compiler calls the constructor as part of the code it generates to create and initialize an object of class type. Objects can be created in several different ways:

  • With automatic variables and constants in a function body:

    void demo(  )
    {
      point p1(1, 2);
      const point origin;
      ...
  • With static objects that are local or global:

    point p2(3, 4);
    void demo(  )
    {
       static point p3(5, 6);
       ...
  • Dynamically, with new expressions:

    point *p = new point(7, 8);
  • With temporary objects in expressions:

    set_bounds(point(left, top), point(right, bottom));
  • With function parameters and return values:

    point offset(point p) { p.x(p.x(  ) + 2); return p; }
    point x(1,2);
    x = offset(x); // Calls point(const point&) twice
  • With implicit type conversions:

    point p = 2; // Invokes point(int=0, int=0), then point(const point&)
  • With constructor initializers:

    derived::derived(int x, int y) : base(x, y) {}

In each case, memory is set aside for the object, then the constructor is called. The constructor is responsible for initializing the object's members. Members that are listed in the initializer list are initialized as requested: scalar and pointer types are initialized with their given values; class-type objects are initialized by invoking the appropriate constructors. Class-type objects that are not listed are initialized by calling their default constructors; other types are left uninitialized. Every const member and reference-type member must have an initializer (or you must be willing to accept the default constructor for a class-type const member). Within the body of the constructor, you are free to change any non-const member. Many simple constructors are written with empty bodies:

struct point {
  point(int x, int y) : x_(x), y_(y) {}
  point(  )             : x_(0), y_(0) {}
  point(double radius, double angle) {
    x = radius * cos(angle);
    y = radius * sin(angle);
  }
  ...

figs/acorn.gif

If a constructor is declared with the explicit specifier, it can never be called for an implicit type conversion. An implicit type conversion is commonly used during assignment-like construction. In the following example, two constructors are called: complex(1.0, 0.0) is implicitly called to construct a nameless temporary object, then the copy constructor is called to copy the temporary object into p. (The compiler is allowed to optimize away the copy and initialize p directly. Most modern compilers perform this optimization, but point must have a copy constructor even if it is never called.)

struct complex {
  complex(double re, double im = 0.0);
  ...
};
complex z = 1;

Implicit type conversions can produce some surprising results by calling conversion constructors when you least expect it. In the following example, a nameless temporary complex object is created as complex(42.0, 0.0), and this temporary object is bound to the z parameter in the call to add2.

complex add2(const complex& z) {
  z.real(z.real(  ) + 2);
  return p;
}
z = add2(42.0);

To avoid unexpected constructor calls, declare a constructor with the explicit specifier if that constructor can be called with a single argument. Calls to an explicit constructor require function-like construction or an explicit type cast:

struct complex {
  explicit complex(double re, double im = 0.0);
  ...
};
complex z = 1;                   // Error
complex z(1);                    // OK
add2(42);                        // Error
add2(static_cast<complex>(42));  // OK
add2(p);                         // OK
6.3.4.4 Throwing exceptions during construction

A constructor can also use a function try block as its function body. The try keyword comes before the initializers. If an exception is thrown during the initialization of any member, the corresponding catch blocks are examined for matching handlers in the usual manner. (See Chapter 4 for information about try blocks and exception handlers.) Example 6-12 shows a constructor with a function try block.

Example 6-12. Catching exceptions in constructors
struct array {
  // Initialize the array with all the items in the range (first, last). If an
  // exception is thrown while copying the items, be sure to clean up by
  // destroying the items that have been added so far.
  template<typename InIter>
  array(InIter first, InIter last)
  try
    : data_(0), size_(0)
  {
    for (InIter iter(first); iter != last; ++iter)
      push_back(*iter);
  } catch (...) {
    clear(  );
  }
  ...
private:
  int* data_;
  std::size_t size_;
};

Regardless of whether the function body is a plain compound statement or a function try block, the compiler keeps track of which base-class subobjects and which members have been initialized. If an exception is thrown during initialization, the destructors of only those objects that have been fully constructed are called.

If the object is being created as part of a new expression, and an exception is thrown, the object's memory is deallocated by calling the appropriate deallocation function. If the object is being created with a placement new operator, the corresponding placement delete operator is called—that is, the delete function that takes the same additional parameters as the placement new operator. If no matching placement delete is found, no deallocation takes place. (See Chapter 3 for more information on new and delete expressions.)

Example 6-13 shows a silly class that allocates two dynamic arrays in its constructor. Suppose the first allocation fails (member str_). In that case, the compiler knows that str_ has not been initialized and so does not call the auto_ptr<> destructor, nor does the compiler call the destructor for the wstr_ member. If, however, the allocation for str_ succeeds and fails for wstr_, the compiler calls the auto_ptr<> destructor for str_ to free the memory that had been allocated successfully. Note that if auto_ptr<> were not used, the class would have a memory leak because the compiler would not be able to free the memory for str_. (Pointers don't have destructors.)

Example 6-13. Exception-safe constructor
struct silly {
  silly(std::size_t n)
  : size_(n), str_(new char[n+1]), wstr_(new wchar_t[n+1])
  {}
  silly(const silly& that);
  silly& operator=(silly that);
private:
  std::size_t size_;
  std::auto_ptr<char> str_;
  std::auto_ptr<wchar_t> wstr_;
};

6.3.5 Destructors

A destructor finalizes an object when the object is destroyed. Typically, finalization frees memory, releases resources (such as open files), and so on. A destructor is a special member function. It has no return type, no arguments, and cannot return a value. A destructor can be virtual, it can be inline, but it cannot be static, const, or volatile. The name of a destructor is a tilde (~) followed by the class name. A class has only one destructor (although it may have several constructors).

Just as constructors automatically construct base-class objects, a destructor automatically calls destructors for nonstatic data members, direct base classes, and virtual base classes after the derived-class destructor body returns. Member and base-class destructors are called in reverse order of their constructors. If you do not write a destructor, the compiler does so for you. (See the next section, Section 6.3.6.) Any class that has virtual functions should have a virtual destructor, as explained in Section 6.4 later in this chapter.

You can call a destructor explicitly, but there is rarely any reason to do so. The compiler calls destructors automatically when objects are destroyed. Dynamically allocated objects are destroyed by delete expressions. Static objects are destroyed when a program terminates. Other named objects are destroyed automatically when they go out of scope. Temporary, unnamed objects are destroyed automatically at the end of the expression that created them.

When you write a destructor, make sure that it never throws an exception. When an exception causes the stack to be unwound (see Chapter 5), objects are automatically destroyed. If a destructor throws an exception during stack unwinding, the program terminates immediately. (See <exception> in Chapter 13.)

Example 6-14 shows how a simple destructor is declared.

Example 6-14. Declaring a destructor
class string {
public:
  string(std::size_t n, char c = '\0')
  : str_(new char[n+1]), size_(n) {
    std::fill(str_, str_+size_+1, c);
  }
  string(  ) : str_(new char[1]), size_(0) {
    str_[0] = '\0';
  }
  ~string(  ) { delete[] str_; }
  const char* c_str(  ) const { return str_; }
  std::size_t size(  )       const { return size_; }
private:
  std::size_t size_;
  char*  str_;
};

6.3.6 Implicit Member Functions

The compiler implicitly declares and defines default and copy constructors, the copy assignment operator, and the destructor in certain circumstances. All these implicit functions are inline public members. This section describes what each function does and the circumstances under which the function is implicitly declared and defined. Example 6-15 explicitly shows how each implicit member function is defined.

Example 6-15. Implicit member functions
// These classes show explicitly what the implicit member functions would be.
class base {
public:
  base(  ) {}
  base(const base& that) : m1_(that.m1_), m2_(that.m2_) {}
  base& operator=(const base& that) {
    this->m1_ = that.m1_;
    this->m2_ = that.m2_;
   return *this;
  }
  ~base(  ) {}
private:
  int m1_;
  char* m2_;
};

class demo {
public:
  demo(  ) {}           // Default constructs three base objects
  demo(demo& d) {}      // Copies three base objects in ary_[]
  demo& operator=(const demo& that) {
    this->ary_[0] = that.ary_[0];
    this->ary_[1] = that.ary_[1];
    this->ary_[2] = that.ary_[2];
    return *this;
  }
  ~demo(  ) {}          // Default destructs three base objects
private:
  base ary_[3];
};

class derived : public base {
public:
  derived(  ) : base(  ) {}                  // Constructs m3_[]
  derived(derived& that) : base(that) {}    // Copies m3_[]
  derived& operator=(const derived& that) {
    static_cast<base&>(*this) =
      static_cast<const base&>(that);
    this->m3_[0] = that.m3_[0];
    this->m3_[1] = that.m3_[1];
    this->m3_[2] = that.m3_[2];
    return *this;
  }
  ~derived(  ) {} // Calls ~base(  ), destructs 3 demo objects
private:
  demo m3_[3];
};

For classes whose data members have fundamental, class, or enumeration types, the implicit functions are often adequate. The most common case in which you must implement these functions explicitly is when an object manages pointers to memory that the object controls. In this case, a copy constructor or copy assignment operator must not blindly copy a member that is a pointer, which results in two pointers to the same memory. Instead, you should allocate a new pointer and copy the contents. In such cases, you will often find yourself providing the copy constructor, copy assignment operator, and destructor.

A useful guideline is that if you write one of the three special functions (copy constructor, copy assignment operator, or destructor), you will probably need to write all three.

If you want to store objects in a standard container, you must make sure it has a copy constructor and a copy assignment operator (implicit or explicit). See Chapter 10 for information about the standard containers.

6.3.6.1 Implicit default constructor

The compiler implicitly declares a default constructor if a class has no user-defined constructors. The implicit default constructor calls the default constructor for all base classes and for all nonstatic data members of class type. Other nonstatic data members are left uninitialized. In other words, the behavior is the same as if you wrote the default constructor with no initializers and an empty function body.

6.3.6.2 Implicit copy constructor

The compiler implicitly declares a copy constructor if a class has no copy constructor. If every direct base class and virtual base class has a copy constructor that takes a const reference parameter, and every nonstatic data member has a copy constructor with a const reference parameter, the implicit copy constructor also takes a const reference parameter. Otherwise, the implicit copy constructor takes a plain reference parameter.

In other words, the compiler tries to declare the implicit copy constructor so it takes a const reference parameter. If it cannot because the implicit function would end up calling an inherited or member copy constructor that does not take a const parameter, the compiler gives up and declares a copy constructor that takes a non-const parameter.

An implicit copy constructor calls the copy constructor for each direct base class and each virtual base class and then performs a member-by-member copy of all nonstatic data members. It calls the copy constructor for each member of class type and copies the values for members of nonclass type.

6.3.6.3 Implicit copy assignment operator

The compiler implicitly declares a copy assignment operator (operator=) if a class does not have one. If every direct base class and virtual base class has a copy assignment operator that takes a const reference parameter, and if every nonstatic data member has a copy assignment operator with a const reference parameter, the implicit copy assignment operator also takes a const reference parameter. Otherwise, the implicit copy assignment operator takes a plain reference parameter.

In other words, the compiler tries to declare the implicit copy assignment operator, so it takes a const reference parameter. If it cannot because the implicit function would end up calling an inherited or member copy assignment operator that does not take a const parameter, the compiler gives up and declares a copy assignment operator that takes a non-const parameter.

An implicit copy assignment operator calls the copy assignment operator for each direct base class and each virtual base class and then performs a member-by-member assignment of all nonstatic data members. It calls the copy assignment operator for each member of class type and assigns the values for members of nonclass type.

6.3.6.4 Implicit destructor

The compiler declares an implicit destructor if the programmer does not provide one. If a base class has a virtual destructor, the implicit destructor is also virtual. The implicit destructor is like a programmer-supplied destructor with an empty function body.

    Team LiB   Previous Section   Next Section