The words constructor and destructor are somewhat misleading in that these programmer-supplied functions neither construct nor destroy the objects of the class to which they are applied by the compiler automatically. The constructors of a class serve primarily to initialize the data members of the class object. The destructor primarily frees any resources acquired by the class object during its lifetime.
class ClassName { public: ClassName(double member1 = 0.0, int member2 = 0) : member1_(member1), member2_(member2) { } // or ClassName(double member1 = 0.0, int member2 = 0) { member1_ = member1; member2_ = member2; } protected: double member1_; int member2_; }; // usage in application ClassName c1; // not c1()! c1() is not default constructor! ClassName c2(-0.7, 2); ClassName c3 = Account(3.5); ClassName c4 = 10; // can only specify single (first) argument
If there is any constrcutor taken argument, default constructor need to be provided explicitly. Although there is no appropriate default values for some classes to allow default constructor, in practice it is almost always necessary to provide beacuse the container classes (e.g. vector), array and dynamic array require default constructor to initialize their elements. In this case, the best is to initialize the object to indicate that it is not yet initialized with a valid set of values (NULL for pointer, 0 for number). Then need to program checks within all member functions to guarantee the integrity of the class object prior to its use (user must explicitly set valid value through modify member function).
The difference of member initialization list and assignment of the data members within the body of the constructor is the former is an initialization while the later is an assignment (initialization is done using default constructor). For efficiency, member class object (non-built-in data type) should always be initialized in the member initialization list, or no need to explicitly initialize (use default constructor).
const
and reference data members must always be
initialized in the member initialization list; otherwise, a
compile-time error results. The order of initialization is
determined by the class declaration order of the members,
not the order of member initialization list. Mis-understand
this can lead to difficult-to-uncover error when using one
class member to intialize another. It is recommended to
list members in an initialization list in the order in which
they are declare.
string s1; // initialization string s2("Hello"); // initialization string s3 = s2; // initialization s1 = s3; // assignment
From a purely operational point of view, the difference
between initialization and assignment is that the
former is performed by a constructor while the latter
is performed by operator=
. In other words,
the two processes correspond to different function calls.
The reason for the distinction is that the two kinds of functions must worry about different things. Constructors usually have to check their arguments for validity, whereas most assignment operators can take it for granted that their argument is legitimate (because it has already been constructed). On the other hand, the target of an assignment, unlike an object undergoing construction, may already have resources allocated to it. These resources typically must be released before the new resources can be assigned. Frequently, one of these resources is memory. Before an assignment operator can allocate memory for a new value, it must first deallocate the memory that was allocated for the old value.
Pass-by-value means "call the copy constructor."
C++ provides a default copy constructor that perform a bitwise copy of each of the data members of the class. But when a class contain pointer data member to dynamic allocated memory, copying the bits of pointer does not copy the allocated memory, so need to override it. Argument must be passed by reference, not for efficiency (avoid calling constructor/destructor), but also to avoid recursive copy constructor call! This may also needed when bitwise copy is inappropriate for the correct behavior of the class.ClassName(const ClassName& original); // usage in application ClassName c2(c1), c4 = c3; // just like: int i(-4), j = i; ClassName c5 = Function(c1); // pass/return by value
ClassName
bytes, thus an inefficient use of
storage to copy objects whose size in bytes is large.
The call to the copy constructor may be not obvious from
the source code when it is generated by the compiler.
Accordingly, it is easy for the programmer to forget the
overhead associated with passing or returning by value.
The class designer can disable passing or returning by
value for functions outside the class by defining it
as private
.
void
). But
destructor also does not take arguments and cannot be
overloaded. By default compiler will generate no-operation
destructor if omitted.
~ClassName(void);
f
expects a
String
object as an argument
void f(String s);
but that the programmer forgets
and invokes it with a string constant such as
f("Turandot");
. If the String
class
has this convert constructor
String(const char s[]); // convert constructorthen the compiler invokes the convert constructor for implicit type conversion. The conversion is implicit in that the compiler performs it; the programmer does not need to provide an explicit cast. However, implicit type conversions may lead to unforeseen, very subtle and hard to detect error. The keyword
explicit
, which occurs in a convert constructor's
declaration, may be used to disable implicit type conversions
through a convert constructor. It is generally required only for
constructor taking one argument.
class String { public: explicit String(const char s[]); };
If same portion of code is use in several place in member functions, keep the code in one protected/ private helper function and each member function call it. This ease maintainance.
operator=
will simplify maintainance.
Moreover, it's beneficial to group the default initilization
and cleaning codes as protected constructor helper function.
The downside is less efficient as invoke default constructor
and default initilization before real assignment. Similarly,
default initilization is not required by destructor as
memory has been deallocated, but better included at the end
of cleaning codes to avoid bug. This is the
efficiency-maintenance tradeoff.
class ClassName { public: ClassName(void) { Initialize(); } ~ClassName(void) { Clean(); } ClassName(const ClassName& original) { Initialize(); (*this) = original; // use operator=() } protected: int member1_; double *member2_; Initialize(void) { member1_ = 0; member2_ = NULL; } Clean(void) { if ( member2_ != NULL ) delete member2_; Initialize(); } };
class ClassName { private: ClassName(const ClassName& original); ClassName& operator=(const ClassName& original); };
Protected constrcutor is also commonly declared to indicate that it is intended to be invoked only when the class serves as a base class within an inheritance hierarchy and not as an object to be manipulated directly within the application.
class ClassName { public: member1type GetMember1(void) const { return member1_; } member2type GetMember2(void) const { return member2_; } void SetMember1(member1type member1) { member1_ = member1; } void SetMember2(member2type member2) { member2_ = member2; } protected: member1type member1_; member2type member2_; }; // usage in application c2.SetMember2(c1.GetMember1());
const
object of
this class, this may cause its content to be modified
inadvertently.
class String { public: String(const char *str); operator char *() const { return str_; } char& operator[](int index) const { return str_[index]; } protected: char *str_; }; const String cs("Hello World"); char *str = cs; // call cs.operator char*() strcpy(str, "Hi World!"); // modify what str points to! String s = "I'm not constant"; s[0] = 'x'; // OK cs[0] = 'x'; // modify const string without complaint!If the intent is not allow user to change data member directly, no matter it is const or not, return a
const
pointer to prevent user change the
content of memory pointed after get it.
class ClassName { public: const member1type* GetMember1() const { return (const member1type*)member1_; } protected: member1type *member1_; }; // usage in application ClassName c1; member1type *pc1 = c1.GetMember1(); // error: cannot convert const* to * const member1type *pc2 = c1.GetMember1(); // OK cout << c1.GetMember1() << endl; // OKIf wish to allow user change data member directly if it is not const object, don't declare the member function
const
.
class ClassName { public: member1type* GetMember1(void) { return member1_; } protected: member1type *member1_; }; // usage in application ClassName c1; member1type *pc1 = c1.GetMember1(); // OK const ClassName cc1; const member1type *pc2 = cc1.GetMember1(); // error: member function not const cout << c1.GetMember1() << endl; // OK
Or provide two member functions serve for non-const and const object, such as indexing operator.
Even return const class internal data handles is ill-advised, not only it violates abstraction, but it can cause dangling handle issue on compiler-generated temporary object.class String { public: String(const char *str); operator const char *() const { return str_; } protected: char *str_; }; String GetStr(void) { return "Hello World"; } const char *pc = GetStr(); // pc points to temporary String object // temporary String object destroyed cout << pc; // result undefined
Member function can declare inline either during declaration in class definition or its definition. However, since inline functions must be defined in every text file in which they are called, inline member function that is not defined in class header file must be placed in class definition.
Constant class object can only access constant member
function, that has const
keyword is placed
between the parameter list and the body of the member function
to mark data member as read only. The keyword must be specified
in both its declaration and its definition. Constructors and
destructors are exceptions in that, even though they are never
constant member functions, they can be called by constant class
object. Its const-ness start from the time its construction
completes to the time its destruction starts.
Constant member function can be overloaded with a non-const member function that has the same parameter list. The const-ness of the class object determines which function is invoked.
If the class contains pointer, the pointer cannot be modified but the object which the pointer refer can be modified within a constant member function.class ClassName { public: void Bad(const string& str) const private: char *text_; }; void ClassName::Bad(const string& str) const { text_ = str.c_str(); // error: text_ cannot be modified for (int i=0; i<str.size(); i++) text_[i] = str[i]; // bad style but not an error }
There are two prevailing notions of constness:
class Screen { public: inline void Move(int r, int c) const; private: string screen_; mutable string::size_type cursor_; // mutable member short width_; short height_; }; void Screen::Move(int r, int c) const { if (CheckRange(r, c)) { int row = (r-1) * width_; cursor_ = row + c - 1; // modify cursor_ } }
This is logical, because modifying cursor_
is
necessary to inspect the content of Screen
object
and does not modify the content of the Screen
itself.
A friend may be a namespace function, a member function of another previously defined class or an entire class. In making one class a friend, all the member functions of the friend class are given access to the nonpublic members of the class granting friendship.
Since friends are not members of the class granting friendship, they are not affected by the public, private or protected section in which they are declared within the class body.
expr1 ? expr2 : expr3
would not be able to
guarantee that only one of exper2
and
expr3
was executed.
N::m
neither N
nor
m
are expressions with values; N
and m
are names known to the compiler and ::
performs a (compile time) scope resolution rather than an
expression evaluation. One could imagine allowing overloading
of x::y
where x
is an object rather
than a namespace or a class, but that would contrary to first
appearences - involve introducing new syntax (to allow
expr::expr
). It is not obvious what benefits such
a complication would bring.
class Y { public: void f(); // ... }; class X { Y* p; Y& operator.() { return *p; } // assume that you can overload . void f(); // ... }; void g(X& x) { x.f(); // X::f or Y::f or error? }
if (expression1 && expression2) ...
becomes
if (expression1.operator&&(expression2)) ...
when implemented as member function or
if (operator&&(expression1, expression2)) ...
when implemented as friend function. When a function call
is made, both expressions are evaluated. There is no way
of knowing whether which one will be evaluated first, needless
to say can ensure it is evaluated in left-to-right order.
c1 = c2 + c3;
.
0.0 < temperature1
is evaluated as
operator<(0.0, temperature1)
, if there is a
constructor Temperature(double t);
, it is
evaluated as
operator<(Temperature(0.0),temperature1)
;
using member function, it is evaluated as
(0.0).operator<(temperature1)
, result
compilation error. Symmetric operators, such as operator==,
are best defined as friend function if either operand can
be of class type while implementation using member function
is safer as it forces explicit conversion:
Temperature(0.0) < temperature1
.
c1 = c2 = c3;
(evaluated as c1.operator=(c2.operator(c3));
).
Self-test is a must not only for speed reason, but also
avoid aliasing (having two or more names for the same
underlying object) and subsequently catastrophic delete
(if pointer data member dynamically allocate memory) in
self-assignment situations.
ClassName& Classname::operator=(const ClassName& original) { if (this == &original) return *this; // self-test member1_ = original.member1_; // copy scalar member // copy other data member ... return *this; // return this object } ClassName& c1 = c2, *pc3 = &c3; *pc3 = c3; // self-assignment c2 = c1; // or vice versaThe problems of aliasing and object identity are hardly confined to assignment member function. In the presence of references and pointers, any two names for objects of compatible types may in fact refer to the same object. Since it can crop up in any number of nefarious disguises, need to take it into account any time write a function (want to self-test or not?).
class BaseName { void mf1(BaseName& rb); // rb and *this could be the same }; void f1(BaseName& rb1, BaseName& rb2); // rb1 and rb2 could be the same class DerivedName: public BaseName { void mf2(BaseName& rb); // rb and *this could be the same }; int f2(DerivedName& rd, BaseName& rb); // rd and rb could be the same
char& basic_string::operator[](int position); // can be rvalue or lvalue const char& basic_string::operator[](int position) const; // rvalue only
cout << c1 << c2;
(evaluated
as operator<<(operator<<(cout,c1),c2);
).
friend ostream& operator<<(ostream& outStream, const ClassName& c) { // format and output statements ... return outStream; } friend istream& operator>>(istream& inStream, ClassName& c); friend fstream& operator<<(fstream& outStream, const ClassName& c); friend fstream& operator>>(fstream& inStream, ClassName& c);Generally to allow chaining operation need to return a reference to itself. This is suitable to member function that originally
void
.
Screen& Screen::Clear(const char bkground = ' ') { // ... return *this; } //... // usage in application screen1.Clear().Move(2, 2).Set('*').Display();
c1 = (c2 + c3)++; c1++++;
c1 = c2 + c3;
)
const ClassName operator+(const ClassName& lhs, const ClassName& rhs) { ClassName result; // addition statements ... return (const ClassName)result; // will call copy constructor } const ClassName operator-(const ClassName& lhs, const ClassName& rhs); const ClassName operator*(const ClassName& lhs, const ClassName& rhs); const ClassName operator/(const ClassName& lhs, const ClassName& rhs);C++ compiler distinguish prefix and postfix increment and decrement operation by
ClassName& operator++(void); // prefix, ++c const ClassName operator++(int); // postfix c++ ClassName& operator--(void); // --c const ClassName operator--(int); // c-- ClassName& operator+=(int); ClassName& operator-=(int);Prefix increment should be implemented as "increment and fetch" while postfix form as "fetch and increment".
// prefix form LargeInt& LargeInt::operator++() { *this += 1; // increment return *this; // fetch } // postfix form const LargeInt LargeInt::operator++(int) // no use of parameter { LargeInt oldValue = *this; // fetch ++(*this); // increment, implemented in terms of their prefix return oldValue; // return what was fetche }
This indicates that when dealing with user-defined types, prefix form is preferable because it is inherently more efficient.
void Add(ClassName& result, const ClassName& lhs, const ClassName& rhs) { // addition statements ... }
Some compiler could recognize the return of the class object and provide the return value transformation, eliminating both the return by value of the class object and the need to invoke the class copy constructor.
ClassName Function(parametertype parameter) { ClassName result; // ... return result; } // transform into void Function(Classname& result, parametertype parameter) { // result = ... }To trigger it, the class object returned at each return point within the function must be the same named object.
Initialization of a class object
ClassName c3 = c1 + c2;
is always more
efficient than its assignment
ClassName c3; c3 = c1 + c2;
. The reason is
ClassName c3 = opeartor+(c1, c2);
can be
safely transformed into
ClassName c3; operator+(c3, c1, c2);
but
not safe to transform
ClassName c3; c3 = opeartor+(c1, c2);
into
operator+(c3, c1, c2);
. The problem is
the transformed function requires the object passed to
it to represent raw storage, so can apply the
constructor that had been applied to the named local
object. If the object being passed in has already been
constructed, then it is potentially semantically
disastrous to construct it a second time. Instead, the
compiler will create raw storage in the form of
temporary class object to pass to the function and
destruct temporary object when it returns.
This is the object identity issue: what it means for two objects to be "the same." The first definition is more common, probably because it is easy to implement and the computation is fast, neither of which is always true when object identity is based on values (Recall the self-test in copy constructor and assignment member function). Choose appropriate definition. Generally identity test does not require operator== to compare in object level, just compare their addresses. If it is treated as relational operator, then it will be equality test.
// relational operation bool operator==(const ClassName& rhs); bool operator!=(const ClassName& rhs); bool operator<(const ClassName& rhs); bool operator<=(const ClassName& rhs); bool operator>(const ClassName& rhs); bool operator>=(const ClassName& rhs);All the relational operators can be implemented using operator== and operator<.
Static data member and member function is class member (belong to the class as a whole), as opposed to object member (belong to individual objects in the class). It can be declared inside the class declaration, but still need to define globally. As with any global object, only one static data member definition can be provided in a program. This means that static data member initialization should not be placed in header files, but rather in the files containing definitions of the class non-inline member functions.
Any member function can access static data member, but
static member function can only access static data
members, not non-static data members (as it don't have
this
pointer). Its benefit is no need
class member access operator to call the function (since
none of non-static data members are ever accessed or
modified, it is really irrelavant). Static member
function can also be accessed even if no class objects
are ever declared.
class ClassName { public: static unsigned GetN(void) const { return n_; } // inline static static2type GetStatic2(void); private: static unsigned n_; // count of ClassName objects static static2type static2_; }; unsigned ClassName::n_ = 10; static2type ClassName::static2_; static2type ClassName::GetStatic2(void) { // non-inline return static2_; }If the static data member or member function is
public
, it can be accessed through either
class objects or directly.
ClassName c1; c1.GetN(); // or ClassName::GetN(); unsigned n1 = c1.n_; // or unsigned n2 = ClassName::n_;
Static local variable in member function is a underlying cell shared by all class objects when they invoke the function. So it is actually similar to static data members except it is in function scope.
void* operator new(size_t size); // size in bytes void* operator new[](size_t size); // size in bytes void* operator new(size_t size, ClassName *p); // for placement new void* operator new[](size_t size, ClassName *p); // for placement new[] // either void operator delete(void *p); void operator delete[](void *p); // or void operator delete(void *p, size_t size); void operator delete[](void *p, size_t size); // for exception handling of executing new expression void operator delete(void *p, ClassName *p); void operator delete[](void *p, ClassName *p);
A new
expression that creates an array first
calls the operator new[] to allocate the storage and
then calls the default constructor to initialize iteratively
every element of the array; a delete
expression
that deletes an array first calls the class destructor to
destroy iteratively every element of the array and then calls
the operator delete[] to deallocate the storage.
#include <new>
)
is used to construct object from already pre-allocated memory.
It allows to invoke constructor directly, useful for
applications using shared memory or memory-mapped I/O, because
objects in such applications must be placed at specific
addresses or in memory allocated by special routines. The
downside is, need to manually call destructor when want them
to go out of existence.
// allocate enough raw memory for an array of 10 ClassName objects void *buffer = operator new[](10 * sizeof(ClassName)); // cArray point to it so it can be treated as an ClassName array ClassName *cArray = static_cast<ClassName*>(buffer); // initialize the ClassName objects using placement new for (int i=0; i<10; ++i) new (&cArray[i]) ClassName(IDNum[i]); // ... for (int i=9; i>=0; --i) cArray[i].~ClassName(); // deallocate the raw memory operator delete[](buffer);
Operator-> and *(dereference) often overloaded by smart pointer.
operator type();
, where type
is
either built-in type, class type or typedef name (not array
or function type). Conversion function must be member function.
Its declaration must not specify a return type nor parameter
list. For example,
operator int() { return static_cast<int>(val); }
.
operator cchar
is const char*
conversion function. Another way is use single-argument
constructor as convert constructor.
class B; // forward declaration for class B class A { public: A(const B&); // an A can be constructed from a B }; class B { public: operator A(void) const; // a B can be converted to an A }; // usage in application void f(const A&); B b; f(b); // compiler error: ambiguous
If the target of the conversion (e.g. double) does not match the type of the conversion function (e.g. int) exactly, conversion function can still be invoked if the target type can be reached through a standard conversion sequence. User-defined conversions are applied implicitly by the compiler. This may cause many viable functions during member function overload resolution.
Conversion operator can lead to the wrong function being
called. The solution is to replace it with equivalent
functions that don't have the syntactically magic names,
like B::toA(void)
. Convert constructor is
even worse, as it quietly creates new object result of
mistyping function (can be operator overloading function)
parameter. That is why explicit
keyword is introduced.
enum
(the enum hack
should not be used anymore), but in fact can use static data
member declaration style and C++ standard support
initialization within the class body with a constant integral
value (but not Visual C++). Even this case, the constant
integral data member must still be defined outside the class
body and must not specify an initial value (as already
initialized).
#include <string> using namespace std; class ClassName { public: ClassName(void) : STR2_("FLOATING") { // other initialization } ClassName(const string& str1) : STR2_(str1) { // other initialization } string GetStr1(void) { // no need const string, but must const string& return STR2_; } private: enum { ONE = 1 }; static const int TWO; static const short THREE = 3; // VC++ not support static const string STR1_; const string STR2_; }; // in implementation file const int ClassName::TWO = 2; const short ClassName::THREE; // definition needed, but no initialization const string ClassName::STR1_ = "FLOATING2";
Why do these inconvenient restrictions exist? A class is typically declared in a header file and a header file is typically included into many translation units. To avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.
new
always returns pointers
to distinct objects.
class Empty { }; int main() { Empty a, b; if (&a == &b) cout << "impossible: report error to compiler supplier"; Empty* p1 = new Empty; Empty* p2 = new Empty; if (p1 == p2) cout << "impossible: report error to compiler supplier"; delete p1; delete p2; return 0; }
this
pointer that
addresses the object for which the member function is called.
Its type in non-const member function is pointer to the class
type, pointer to const
class type in constant
member function and pointer to volatile
class type
in a volatile
member function. When an object call
the member function, this
points to its data members.
ClassName * const this; // for non-const member function const ClassName * const this; // for const member function c1.SetMember1(member1); // is translate to SetMember1(&c1, member1) { this->member1_ = member1; }
To understand what and why have nested class (nested in another class) and local class (defined inside a function body), think them as struct (struct and class is the same in C++) only used inside a class and function. They normally simple (basically data structure and some helper function) and mostly public members (easy for its host to access). Typical example is list node struct/ class inside list class.
The rule of thumb of designing simple interface is only data or functions that are taking any responsibility for maintaining the invariant should be defined as class. The invariant is the valid state of the member variables that need to keep track. Just using the data or functions, but not defending the invariant, doesn't need to be in the class, better be struct or function built on top of existing class.
If the class is full of Set
and
Get
member functions, it seem like an
unnecessary layer of abstraction to the data. If there
is too many dynamic cast casting from base classes to
derived classes, this indicates that there might be
too much class hierarchies.
The way the whole thing is conceived is that the constructor establishes the environment for the member functions to operate in, in other words, the constructor establishes the invariant. And since to establish the invariant often have to acquire resources, the destructor will pull down the operating environment and release any resources required.
For example, in vector
, size and elements
that it hold much be matched. If the function changes the
size of a vector, then it need to changes the number of
elements stored and move the elements. There must be a
member function for that. But given efficient element
access, find
function for searching in a
vector is best provided as a non-member.
In Date
class, functions such as
find_next_Sunday
should not be member function,
else the class will be crowded making hard to change the
data layout.