The use of structs is also discouraged since these only contain public data. In interfaces with other languages (such as C), it may, however, be necessary to use structs.
Example 22 The correct way to encapsulate data so that future changes are possible.
// Original class:
class Symbol {};
class OldSymbol : public Symbol {};
class Priority
{
  public:
    // returns pd int priority();
    // returns symbol class Symbol* getSymbo1() const;
    // ...
  private:
    int pd;
    O1dSymbol symbol;
};
This shows why member functions should be used to access data (instead
of using direct references).This usage provides long term advantages, since
internal data in a class may be changed without having to modify interfaces
and to re-write the code which uses them.
// Modified class:
// The programmer has chosen to change the private data from an int
// to an enum. A user of the class 'Priority' does not have to change
// any code, since the enum return-value from the member function
// priority() is automatically converted to an int.
class Symbol {};
class NewSymbol : public Symbol {};
enum Priority { low, high, urgent };
class Priority
{
  public:
    // Interface intact through implicit cast, returns priority_data
    Priority priority();
    // Interface intact, object of new subclass to symbol returned class
    Symbo1* getSymbol () const;// ...
  private:
    Priority priority_data; // New representation/name of internal data
    NewSymbol symbol;
};
Small functions, such as access functions, which return the value of a member of the class and so-called forwarding functions which invoke another function should normally be inline.
Correct usage of inline functions may also lead to reduced size of code.
Warning: functions, which invoke other inline functions, often become too complex for the complier to be able to make them inline despite their apparent smallness.
This problem is especially common with constructors and destructors. A constructor always invokes the constructors of its base classes and member data before executing its own code. Always avoid inline constructors and destructors!
A friend is a non-member of a class, that has access to the non-public members of the class. Friends offer an orderly way of getting around data encapsulation for a class. A friend class can be advantageously used to provide functions which require data that is not normally needed by the class.
Suppose there is a list class which needs a pointer to an internal list element in order to iterate through the class. This pointer is not needed for other operations on the list. There may then be reason, such as obtaining smaller list objects, for an list object not to store a pointer to the current list element and instead to create an iterator, containing such a pointer, when it is needed.
One problem with this solution is that the iterator class normally does not have access to the data structures which are used to represent the list (since we also recommend private member data).
By declaring the iterator class as a friend, this problem is avoided without violating data encapsulation.
Friends are good if used properly. However, the use of many friends can indicate that the modularity of the system is poor.
Non-const member functions are sometimes invoked as so-called 'lvalues[1] ' (as a location value at
which a value may be stored). A const member function may never be invoked as an 'lvalue'.
The behaviour of an object can be affected by data outside the object. Such data must not be modified by a const member function,
At times, it is desirable to modify data in a const object (such a having a cache of data for performance reasons). In order to avoid explicit type conversions from a const type to a non-const type, the only way is to store the information outside the object. (See example 55). This type of data should be seen as external data which does not affect the behaviour of the class.
Example 23 const-declared access functions to internal data in a class
class SpecialAccount : public Account
{
  public:
    int insertMoney () ;
    // int getAmountOfMoney () ; No! , Forbids ANY constant object to
    //                          access the amount of money.
    int getAmountOfMoney () const; // Better!
    // ...
  private:
    int moneyAmount;
};
Example 24 Overloading an operator/function with respect to const-ness
#include <iostream.h>
#include <string.h>
static unsigned const cSize = 1024;
class InternalData {};
class Buffer
{
  public:
    Buffer( char* cp );
    // Inline functions in this class are written compactly
    // so the example may fit on one page. THIS is NOT to be
    // done in practice (See Rule 21).
    // A. non-const member functions: result is an lvalue
    char& operator [] ( unsigned index ) { return buffer [ index] ; }
    InternalData& get () { return data; }
    // B. const member functions: result is not an lvalue
    char operator [] ( unsigned index ) const { return buffer [index]; }
    const InternalData& get () const { return data; }
  private:
    char buffer[cSize];
    InternalData data;
};
inline Buffer::Buffer( char* cp )
{
  strncpy( buffer , cp , sizeof( buffer ) );
}
main()
{
  const Buffer cfoo = "peter"; // This is a constant buffer
  Buffer foo = "mary"       // This buffer can change
  foo[2]='c'                 // calls char& Buffer::operator [] (unsigned)
  cfoo[2] = 'c'             // ERROR: cfoo [2] is not an lvalue.
  // cfoo[2] means that Buffer::operator[](unsigned) const is called.
  cout << cfoo[2] << ":" << foo[2] << endl; // OK! Only rvalues are needed
  foo.get() = cfoo.get();
  cfoo.get() = foo.get()       // ERROR: cfoo.get () is not an lvalue
}
The corresponding problem exists for the assignment operator ('='). See 7.6: Assignment Operators.
If a class, having virtual functions but without virtual destructors, is used as a base class, there may be a surprise if pointers to the class are used. If such a pointer is assigned to an instance of a derived class and if delete is then used on this pointer, only the base class' destructor will be invoked. If the program depends on the derived class' destructor being invoked, the program will fail.[3]
In connection with the initialization of statically allocated objects, it is not certain that other static objects will be initialized (for example, global objects).[4] This is because the order of initialization of static objects which is defined in various compilation units, is not defined in the language definition.
There are ways of avoiding this problem[5] , all of which require some extra work.
| You must know what you are doing If you invoke virtual functions from a constructor in the class, If virtual functions in a derived class are overridden, the original definition in the base class will still be invoked by the base class' constructor. Override, then, does not always work when invoking virtual functions in constructors. See Example 30. |
Example 25 Definition of a "dangerous" class not having a copy constructor
#include <string.h>
class String
{
  public :
    String ( const char* cp = "') // Constructor
    ~String ()                   // Destructor
    // ...
  private:
    char* sp;
    // ...
};
String::String(const char* cp) : sp( new char[strlen(cp)] ) // Constructor
{
  strcpy (sp, cp) ;
}
String::~String()  // Destructor
{
  delete sp;
}
// "Dangerous" String class
void
main()
{
  String wl;
  String w2 = w1;
  // WARNING: IN A BITWISE COPY OF wl::sp,
  // THE DESTRUCTOR FOR W1::SP WILL BE CALLED TWICE:
  // FIRST, WHEN wl IS DESTROYED; AGAIN, WHEN w2 IS DESTROYED.
}
Example 26 "Safe" class having copy constructor and default constructor
#include <string.h>
class String
{
  public:
    String( const char* cp = "") // Constructor
    String( const String& sp )   // Copy constructor
    ~String()                     // Destructor
    // ...
  private:
    char* sp;
    // ...
} ;
String::String( const char* cp ) : sp( new char[strlen(cp)] ) // Constructor
{
  strcpy(sp,cp);
}
String::String( const String& stringA ) : sp( new char[strlen(stringA.sp)] )
{
  strcpy (sp, stringA.sp) ;
}
String::~String()    // Destructor
{
  delete sp;
}
// "Safe" String class
void
main()
{
  String w1;
  String w2 = w1; // SAFE COPY: String::String( const String& ) CALLED.
}
Example 27 Definitions or classes not having virtual destructors
class Fruit
{
  public:
    ~Fruit () ; // Forgot to make destructor virtual!!
    // ...
};
class Apple : public Fruit
{
  public:
    ~Apple()   // Destructor
    // ...
};
// "Dangerous" usage of pointer to base class
class FruitBasket
{
  public:
    FruitBasket()     // Create FruitBasket
    ~FruitBasket()   // Delete all fruits
    // ...
    void add(Fruit*)   // Add instance allocated on the free store
    // ...
  private:
    Fruit* storage[42] // Max 42 fruits stored
    int numberOfStoredFruits;
};
void
FruitBasket::add(Fruit* fp)
{
  // Store pointer to fruit
  storage[numberOfStoredFruits++] = fp;
}
FruitBasket::FruitBasket() : numberOfStoredFruits(0)
{
}
FruitBasket::~FruitBasket()
{
  while (numberOfStoredFruits > 0)
  {
  delete storage[--numberOfStoredFruits]; // Only Fruit::~Fruit is called!!
  }
}
Example 28 Dangerous use of static objects in constructors
// Hen.hh
class Egg;
class Hen
{
  public:
    Hen()   // Default constructor
    ~Hen() // Destructor
    // ...
    void makeNewHen(Egg*);
    // ...
};
// Egg.hh
class Egg { };
extern Egg theFirstEgg   // defined in Egg.cc
// FirstHen.hh
class FirstHen : public Hen
{
  public:
    FirstHen()   // Default constructor
    // ...
};
extern FirstHen theFirstHen // defined in FirstHen.cc
// FirstHen.cc
FirstHen theFirstHen // FirstHen::FirstHen() called
FirstHen::FirstHen()
{
// The constructor is risky because theFirstEgg is a global object
// and may not yet exist when theFirstHen is initialized.
// Which comes first, the chicken or the egg ?
  makeNewHen (&theFirstEgg) ;
}
Example 29 One way of ensuring that global objects have been initialized
// WARNING !!! THIS CODE IS NOT FOR BEGINNERS !!!
// PortSetup.hh
class PortSetup
{
  public:
    PortSetup() // Constructor: initializes flag
    void foo()   // Only works correctly if flag is 42
  private:
    int flag     // Always initialized to 42
} ;
extern PortSetup portSetup; // Must be initialized before use
// Create one instance of portSetupInit in each translation unit
// The constructor for portSetupInit will be called once for each
// translation unit. It initializes portSetup by using the placement
// syntax for the "new" operator.
static
class PortSetupInit
{
  public:
    PortSetupInit()           // Default constructor
  private:
    static int isPortSetup;
} portSetupInit;
// PortSetup.cc
#include "PortSetup.hh"
#include <new.h>
// ...
PortSetupInit::PortSetupInit()  // Default constructor
{
  if ( !isPortSetup)
  {
    new (&portSetup) PortSetup;
    isPortSetup = 1;
  }
}
Example 30 Override of virtual functions does not work in the base class'
constructors
class Base
{
  public:
    Base()       // Default constructor
    virtual void foo() { cout << "Base::foo" << endl; }
    // ...
};
Base::Base()
{
  foo()     // Base::foo() is ALWAYS called.
}
// Derived class overrides foo()
class Derived : public Base
{
  public:
    virtual void foo() {cout<<"Derived::foo"<<endl;} //foo is overridden
    // ...
};
main()
{
  Derived d; // Base::foo() called when the Base-part of
            // Derived is constructed.
}
One consequence of this is that bit-wise copying is performed for member data having pointer types. If an object manages the allocation of the instance of an object pointed to by a pointer member, this will probably lead to problems: either by invoking the destructor for the managed object more than once or by attempting to use the deallocated object. See also Rule 25.
If an assignment operator is overloaded, the programmer must make certain that the base class' and the members' assignment operators are run.
A common error is assigning an object to itself (a = a). Normally, destructors for instances which are allocated on the heap are invoked before assignment takes place. If an object is assigned to itself, the values of the instance variables will be lost before they are assigned. This may welllead to strange run-time errors. If a = a is detected, the assigned object should not be changed.
If an assignment operator returns "void", then it is not possible to write a = b = c. It may then be tempting to program the assignment operator so that it returns a reference to the assigning object, Unfortunately, this kind of design can be difficult to understand. The statement ( a = b) = c can mean that a or b is assigned the value of c before or after a is assigned the value of b. This type of code can be avoided by having the assignment operator return a const reference to the assigned object or to the assigning object. Since the returned object cannot be placed on the left side of an assignment, it makes no difference which of them is returned (that is, the code in the above example is no longer correct).
Example 31 Incorrect and correct return values from an assignment operator
void MySpecialClass::operator=( const MySpecialClass& msp )   // well ....? MySpecialClass& MySpecialClass::operator=( const MySpecialClass& msp )   // No! const MySpecialClass& MySpecialClass::operator=( const MySpecialClass& msp )   // RecommendedExample 32 Definition of a class with an overloaded assignment operator
class DangerousBlob
{
  public:
    const DangerousBlob& operator=( const DangerousBlob& dbr );
    // ...
  private:
    char* cp;
} ;
// Definition of assignment operator
const DangerousBlob&
DangerousBlob::operator=( const DangerousBlob& dbr )
{
  if ( this = &dbr ) // Guard against assigning to the "this" pointer
  {
    delete cp     // Disastrous if this == &dbr
  }
  // ...
}
Designing a class library is like designing a language ! If you use operator overloading, use it in a uniform manner; do not use it if it can easily give rise to misunderstanding.
If the operator != has been designed f or a class, then a user may well be surprised if the operator == is not defined as well.
A worse risk is having pointers which point to deallocated memory. Rule 29 and Rule 30 attempt to avoid this situation.
Note that we do not forbid the use of protected member functions which return a const reference or pointer to member data. If protected access functions are provided, the problems described in 7.1 are avoided.
Example 33 Never return a non-const reference to member data from a public function.
class Account
{
  public:
    Account ( int myMoney ) : moneyAmount ( myMoney ) {};
    const int& getSafeMoney () const { return moneyAmount; }
    int& getRiskyMoney () const { return moneyAmount; }    // No !
    // ...
  private:
    int moneyAmount;
};
Account myAcc (10) ; // I'm a poor lonesome programmer a long way from home
myAcc.getSafeMoney() += 1000000 //Compilation error:assignment to constant
myAcc.getRiskyMoney () += 1000000; // myAcc: :moneyAmount = 1000010 ! !
A derived class often requires access to base class member data in order to create useful member functions. The advantage in using protected member functions is that the names of base class member data are not visible in the derived classes and thus may be changed. Such access functions should only return the values of member data (read-only access). This is best done by simply invoking const functions for the member data.
The guiding assumption is that those who use inheritance know enough about the base class to be able to use the private member data correctly, while not referring to this data by name. This reduces the coupling between base classes and derived classes.
[0]1 Not completely true, If a class has a member function which returns a reference to a data member variables
[1] , See, for example, page 25 in ref, [l]: The Annotated C++ Reference Manual - Bjarne Stroustrup/Margareth Ellis[ARM],
[1] , i.e. instances bound to member variables of pointer or reference type that are deallocated by the object
[2] , See Example 25 and Example 26
[3] , See Example 27
[4] , i,e. the static object which was declared external, See Example 28
[5] See Example 29
[1] i.e, instances bound to member variables of pointer or reference type that are deallocated by the object
[1] As opposed to the language Eiffel, in which multiple, repeated inheritance is permit.