I-PROGR2-HOME

  1. Einführung

  2. Initialisierung von C++-Objekten

  3. Default Constructor, Deconstructor, CopyConstructor und CopyAssignment Operator

    1. Constructor

    2. Deconstructor

    3. Copyconstructor

    4. Copy Assigment Operator

    5. Bsp. Konstruktor, Copy-Assigment-Konstruktor, Copy-Konstruktor

  4. Flache und tiefe Implementierung

  5. Eigene Zuweisungs-Operatoren

  6. Testfragen und Aufgaben


I-PROGR2 SS03 - PROGRAMMIEREN2 - Vorlesung mit Übung
Destruktor, Copy-Konstruktor und weitere Operatoren

 Achtung : Skript gibt den mündlichen Vortrag nicht vollständig wieder !!!

                         

AUTHOR: Gerd Döben-Henisch
DATE OF FIRST GENERATION: April-8, 2003
DATE OF LAST CHANGE: Oct-6, 2003
EMAIL: Gerd Döben-Henisch



1. Einführung


Nachdem in der vorausgehenden Vorlesung an einem ersten Beispiel gezeigt worden ist, wie eine nach objektorientierten Grundsätzen erarbeitete Struktur von Objekten mit den Mitteln von C++ implementiert werden kann, sind nun noch einige Besonderheiten von C++-Klassen nachzutragen, bevor weitere Implementierungen mit C++ ins auge gefasst werden.


START



2. Initialisierung von C++-Objekten


Die nachfolgende einfache Klasse enthält einen Konstruktor Basics(int,int) mit zwei Argumenten, d.h. bei Erzeugung eines Objektes für diese Klasse müssen zwei Anfangswerte vom Typ int übergeben werden.

Die Klasse enthält zusätzlich einen Dekonstruktor ~Basics(), der dazu da ist, einen Pointer samt dem zugehörigen reservierten Speicher bei Beendigung explizit zu beseitigen.

Neben den beiden Funktionen write() und read() zum Schreiben und Lesen der Variablen gibt es noch private Variablen vom Typ int mit einem Pointer '*c' vom Typ int.

Da die Klasse nur Prototypen der Funktionen enthält, müssen diese Funktionen extern in einer anderen Datei definiert werden.



#ifndef Basics_HPP
#define Basics_HPP


// A class for demonstrating initialization

class Basics
{
  public:
  Basics(int, int); //Konstructor mit zwei Argumenten
  ~Basics();  //Dekonstruktor wegen dem Pointer (s.u.)

  void write();
  void read();

  private:
  int a,b;
  int *c;  //Achtung! Pointer!
};

#endif



Die Definition --man spricht auch von der Implementierung-- der Funktionsprototypen aus der Klassendefinition findet in der Datei Basics.cpp statt.

Die Klassendefinition muss inkludiert werden.

Alle Elementfunktionen der Klasse Basics, die in dieser Datei Basics.cpp definiert werden sollen, müssen durch Angabe des Bereiches ('scope') als Elementfunktionen kenntlich gemacht werden. Also:

Die Bedeutung eines expliziten Konstruktors liegt darin, beim Erzeugen eines Objektes dem User die Möglichkeit zu bieten, während der Erzeugung eines Objektes bestimmte Startwerte/ Initialisierungswerte mit zu übergeben bzw. durch den Konstruktor erzeugen zu lassen. Das Beispiel zeigt folgende Mechanismen:

Basics::Basics(int init1, int init2)

Beim Aufruf des Konstruktors im späteren User-Programm können Argumente übergeben werden.

 Basics::Basics(int init1, int init2)
  : a(init1), b(init2)   //Initialisierungsliste
 

Die beim Aufruf übergebenen Werte können dazu benutzt werden, um die Elementvariablen der Klasse --auch die als private (oder protected) deklarierten mit den übergebenen Werten in einer eigenen Initialisierungsliste zu initialisieren.

 {
  c = new int(init2);   //Weitere Möglichkei,t zu initialisieren
}

Natürlich kann man auch innerhalb des Funktionsrumpfes Wertzuweisungen vornehmen. Im Beispiel wird mittels new ein neuer Speicher für den Pointer 'c' angefordert und für diesen neuen Speicher wird zur Initialisierung ein Argument aus dem Funktionsaufruf benutzt.

 Basics::~Basics(){

  delete(c);   //Explizite Beseitigung des Pointers
}
 

Der Dekonstruktor sorgt nur dafür, den Pointer samt den zugehörigen reservierten Speicher wieder freizugeben.

Die Funktion write() greift direkt auf die privaten Variablen des Objektes zu und weist ihnen neue Werte zu. Umgekehrt kann die Funktion read() direkt auf die privaten Variablen zugreifen und deren Werte auslesen.


 
#include "Basics.hpp"
#include <iostream>

using namespace std;

Basics::Basics(int init1, int init2)
  : a(init1), b(init2)   //Initialisierungsliste
{
  c = new int(init2);   //Weitere Möglichkei,t zu initialisieren
}

Basics::~Basics(){

  delete(c);   //Explizite Beseitigung des Pointers
}

void Basics::write(){

  cout << "a = ";
  cin >> a;
  cout << endl << "b = ";
  cin >> b;
  cout << "c = ";
  cin >> *c;

}

void Basics::read(){

  cout << "a = "<< a << " b = "<< b<< " c = "<< *c<< endl;

}



Basics m(7,9);
 

Im Hauptprogramm use_basics.cpp wird auf der Basis der Klassendefinition ein Objekt m erzeugt. Bei der Erzeugung/ Instantiierung werden zugleich zwei Startwerte mit übergeben. Diese werden, wie aus der zuvor besprochenen Konstruktor-Funktion ersichtlich, den Variablen a und b zugewiessen, aber auch dem neuen mittels new erzeugten Speicherbereich, auf den der Pointer c verweist.

Die Funktion m.read() liest die werte aus und die Funktion m.write() schreibt neue Werte in die Variablen hinein.




//---------------------------
//
// use_basics.cpp
//
// Author: gerd d-h
// First: April-14, 2003
//
// demonstrates different kinds of initializations
//
//-----------------------------------------------------

#include <iostream>
#include "Basics.hpp"

int main()
{

  Basics m(7,9);  //erzeuge Objekt m vom Typ 'Basics'

  m.read();   //Benutze elementfuntkionen vom Objekt m

    m.write();

    m.read();


    return 0;
}


START



3. Default Constructor, Deconstructor und CopyConstructor; Möglichkeiten und Grenzen


Vergleicht man die Klassendarstellung in UML mit der in C++, dann fallen als Besonderheiten von C++ die unbedingten Operationen Konstruktor, Dekonstrukor, Copykonstruktor sowie Zuweisungsoperator ('assignment operator') auf. Ein Konstruktor 'baut' --salopp gesagt-- ein Objekt 'auf', ein Dekonstruktor 'räumt es wieder weg', ein Copyonstruktor erlaubt das Zuweisen von Werten zu Objekten während der Initialisierung und Zuweisungsoperatoren erlauben das Zuweisen von Werten auch noch nach der Initialisierung.

Dabei wird zwischen den impliziten Versionen dieser Konstruktoren unterschieden und den expliziten. Die impliziten Versionen treten dann auf, wenn der User keine explizite Degfinition dieser Operatoren vornimmt. In diesem Fall werden sie automatisch vom Compiler in 'Basisversionen' erzeugt. Diese setzen Basisdatentypen in einfachen ('trivial') Strukturen voraus. Sobald spezielle Datenstrukturen verwendet werden, insbesondere auch Pointer, sind die impliziten einfachen Versionen unzureichend.

Die folgenden Textausschnitte beschreiben wichtige Eigenschaften der genannten Operatoren aus dem C++-Standard. Für weitere Details und Querverweise sei auf den C++-Standard selbst verwiessen.


START



3.i Constructor


The default constructor , copy constructor and copy assignment operator , and destructor are special member functions. The implementation will implicitly declare these member functions for a class type when the program does not explicitly declare them, .... The implementation will implicitly define them if they are used,... Programs shall not define implicitly-declared special member functions. Programs may explicitly refer to implicitly declared special member functions.

Constructors do not have names. A special declarator syntax using an optional function-specifier followed by the constructor s class name followed by a parameter list is used to declare or define the con-structor. In such a declaration, optional parentheses around the constructor class name are ignored.

[Exam-ple:
class C { public: C(); //declares the constructor };
C::C() { } // defines the constructor  end example]

A constructor is used to initialize objects of its class type. Because constructors do not have names, they are never found during name lookup; however an explicit type conversion using the functional notation will cause a constructor to be called to initialize an object. [Note: for initialization of objects of class type see 12.6. ] 3 A typedef-name that names a class is a class-name; however, a typedef-name that names a class shall not be used as the identifier in the declarator for a constructor declaration.

A constructor shall not be virtual or static. A constructor can be invoked for a const, volatile or const volatile object. A constructor shall not be declared const, volatile, or const volatile. const and volatile semantics are not applied on an object under construction. Such semantics only come into effect once the constructor for the most derived object ends.

A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X, a default constructor is implicitly declared. An implicitly-declared default constructor is an inline public member of its class. A constructor is trivial if it is an implicitly-declared default constructor and if: its class has no virtual functions and no virtual base classes, and all the direct base classes of its class have trivial constructors, and for all the nonstatic data members of its class that are of class type (or array thereof), each such class has a trivial constructor.

Otherwise, the constructor is non-trivial.

An implicitly-declared default constructor for a class is implicitly defined when it is used to create an object of its class type. The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with an empty mem-initializer- list and an empty function body. If that user-written default constructor would be ill-formed, the program is ill-formed. Before the implicitly-declared default constructor for a class is implic-itly defined, all the implicitly-declared default constructors for its base classes and its nonstatic data mem-bers shall have been implicitly defined. ...

Default constructors are called implicitly to create class objects of static or automatic storage duration defined without an initializer, are called to create class objects of dynamic storage duration created by a new-expression in which the new-initializer is omitted, or are called when the explicit type conversion syntax is used. A program is ill-formed if the default constructor for an object is implicitly used and the constructor is not accessible.

No return type (not even void) shall be specified for a constructor. A return statement in the body of a constructor shall not specify a return value. The address of a constructor shall not be taken.

A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.

 [Example:
 class X { // ... public: X(int); X(const char*, int =0); };
 void f(X arg) { X a = 1; // a = X(1)
 X b = "Jessie"; // b = X("Jessie",0)
 a = 2; // a = X(2)
 f(3); // f(X(3)) }
 endexample]
 

An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax or where casts are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization.

[Example:
class Z {
public: explicit Z();
explicit Z(int); // ... };
 Z a; // OK: default-initialization performed
 Z a1 = 1; // error: no implicit conversion
 Z a3 = Z(1); // OK: direct initialization syntax used
 Z a2(1); // OK: direct initialization syntax used
 Z* p = new Z(1); // OK: direct initialization syntax used
 Z a4 = (Z)1; // OK: explicit cast used Z
 a5 = static_cast(1); // OK: explicit cast used
 end example]
 

When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the initializer has the form (), the object is initialized as specified in.... [Note: if the class is a non-POD, it is default-initialized. ]

An object of class type (or array thereof) can be explicitly initialized; ...

When an array of class objects is initialized (either explicitly or implicitly), the constructor shall be called for each element of the array, following the subscript order; ... [Note: destructors for the array elements are called in reverse order of their construction. ]


START



3.ii Deconstructor


A special declarator syntax using an optional function-specifier followed by ~ followed by the destructor s class name followed by an empty parameter list is used to declare the destructor in a class definition. In such a declaration, the ~ followed by the destructor s class name can be enclosed in optional parentheses; such parentheses are ignored. A typedef-name that names a class is a class-name ; however, a typedef-name that names a class shall not be used as the identifier in the declarator for a destructor declaration.

A destructor is used to destroy objects of its class type. A destructor takes no parameters, and no return type can be specified for it (not even void). The address of a destructor shall not be taken. A destructor shall not be static. A destructor can be invoked for a const, volatile or const volatile object. A destructor shall not be declared const, volatile or const volatile. const and volatile semantics are not applied on an object under destruction. Such semantics stop being into effect once the destructor for the most derived object starts.

If a class has no user-declared destructor, a destructor is declared implicitly. An implicitly-declared destructor is an inline public member of its class. A destructor is trivial if it is an implicitly-declared destructor and if: all of the direct base classes of its class have trivial destructors and for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

Otherwise, the destructor is non-trivial.

An implicitly-declared destructor is implicitly defined when it is used to destroy an object of its class type. A program is ill-formed if the class for which a destructor is implicitly defined has: a non-static data member of class type (or array thereof) with an inaccessible destructor, or a base class with an inaccessible destructor. Before the implicitly-declared destructor for a class is implicitly defined, all the implicitly-declared destructors for its base classes and its nonstatic data members shall have been implicitly defined.

A destructor for class X calls the destructors for X s direct members, the destructors for X s direct base classes and, if X is the type of the most derived class, its destructor calls the destructors for X s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor. A return statement in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction.

A destructor can be declared virtual or pure virtual; if any objects of that class or any derived class are created in the program, the destructor shall be defined. If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly- declared) is virtual.

Destructors are invoked implicitly

  1. for a constructed object with static storage duration at pro-gram termination,


  2. for a constructed object with automatic storage duration when the block in which the object is created exits,


  3. for a constructed temporary object when the lifetime of the temporary object ends,


  4. for a constructed object allocated by a new-expression, through use of a delete-expression,


  5. in several situations due to the handling of exceptions.


A program is ill-formed if an object of class type or array thereof is declared and the destructor for the class is not accessible at the point of the declaration. Destructors can also be invoked explicitly.

At the point of definition of a virtual destructor (including an implicit definition), non-placement operator delete shall be looked up in the scope of the destructor s class and if found shall be accessible and unambiguous. [Note: this assures that an operator delete corresponding to the dynamic type of an object is available for the delete-expression. ]

In an explicit destructor call, the destructor name appears as a ~ followed by a type-name that names the destructor s class type. The invocation of a destructor is subject to the usual rules for member functions, that is, if the object is not of the destructor s class type and not of a class derived from the destructor s class type, the program has undefined behavior (except that invoking delete on a null pointer has no effect).

[Note: an explicit destructor call must always be written using a member access operator; in particular, the unary-expression ~X() in a member function is not an explicit destructor call ]

[Note: explicit calls of destructors are rarely needed. One use of such calls is for objects placed at specific addresses using a new-expression with the placement option. Such use of explicit placement and destruction of objects can be necessary to cope with dedicated hardware resources and for writing memory management facilities. For example,

void* operator new(size_t, void* p) { return p; }
struct X { // ... X(int);
~X(); };
void f(X* p);
void g() // rare, specialized use: { char* buf = new char[sizeof(X)];
X* p = new(buf) X(222); // use buf[] and initialize f(p);
p->X::~X(); //cleanup }
end note]

Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended. [Example: if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. ]

[Note: the notation for explicit call of a destructor can be used for any scalar type name. Allowing this makes it possible to write code without having to know if a destructor exists for a given type. For example,

typedef int I; I* p; // ... p->I::~I();
end note]

START



3.iii Copyconstructor


A copy constructor for a class X is a constructor with a first parameter of type X& or of type const X&.

A copy-constructor is a converting constructor. An implicitly-declared copy constructor is not an explicit constructor; it may be called for implicit type conversions.

A class object can be copied in two ways, by initialization, including for function argument passing and for function value return, and by assignment. Conceptually, these two operations are implemented by a copy constructor and copy assignment operator.

A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments

 [Example:
X::X(const X&)
and
X::X(X&, int=1)
are copy constructors.
class X { // ...
public: X(int);
X(const X&, int = 1); };
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
end example]

[Note: all forms of copy constructor may be declared for a class.

[Example:
class X { // ...
 public:
 X(const X&);
 X(X&); //OK };
 end example]
 end note]
 

[Note: if a class X only has a copy constructor with a parameter of type X&, an initializer of type const X or volatile X cannot initialize an object of type (possibily cv-qualified) X.

 [Example:
 struct X { X(); //default constructor
 X(X&); //copy constructor with a nonconst parameter
 };
 const X cx;
 X x = cx; // error
 X::X(X&) cannot copy cx into x
 end example]
 end note]
 

A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to perform the copy of a class object to an object of its class type.

[Example:
struct S {
template S(T);
};
S f();
void g() { S a( f() ); // does not instantiate member template }
end example]

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. Thus, for the class definition struct X { X(const X&, int); }; a copy constructor is implicitly-declared. If the user-declared constructor is later defined as X::X(const X& x, int i =0) { /* ... */ } then any use of X s copy constructor is ill-formed because of the ambiguity; no diagnostic is required.

The implicitly-declared copy constructor for a class X will have the form X::X(const X&) if each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&. Otherwise, the implicitly declared copy constructor will have the form X::X(X&) An implicitly-declared copy constructor is an inline public member of its class./p>

A copy constructor for class X is trivial if it is implicitly declared and if class X has no virtual functions and no virtual base classes, and each direct base class of X has a trivial copy constructor, and for all the nonstatic data members of X that are of class type (or array thereof), each such class type has a trivial copy constructor; otherwise the copy constructor is non-trivial.

An implicitly-declared copy constructor is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type. [Note: the copy constructor is implicitly defined even if the implementation elided its use. ] A program is ill-formed if the class for which a copy constructor is implicitly defined has: a nonstatic data member of class type (or array thereof) with an inaccessible or ambiguous copy con-structor, or a base class with an inaccessible or ambiguous copy constructor. Before the implicitly-declared copy constructor for a class is implicitly defined, all implicitly-declared copy constructors for its direct and virtual base classes and its nonstatic data members shall have been implicitly defined. [Note: an implicitly-declared copy constructor has an exception-specification. ]/p>

The implicitly-defined copy constructor for class X performs a memberwise copy of its subobjects. The order of copying is the same as the order of initialization of bases and members in a user-defined construc-tor. Each subobject is copied in the manner appropriate to its type: if the subobject is of class type, the copy constructor for the class is used; if the subobject is an array, each element is copied, in the manner appropriate to the element type; if the subobject is of scalar type, the built-in assignment operator is used. Virtual base class subobjects shall be copied only once by the implicitly-defined copy constructor.


START



3.iv Copy Assigment Operator


A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&. [Note: an overloaded assignment operator must be declared to have only one parameter;... ] [Note: more than one form of copy assignment operator may be declared for a class. ] [Note: if a class X only has a copy assignment operator with a parameter of type X&, an expression of type const X cannot be assigned to an object of type X.

[Example:
struct X {
X();
X& operator=(X&);
};
const X cx;
X x;
void f() { x = cx; // error: // X::operator=(X&) cannot assign cx into x }
end example]
end note]
 

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. The implicitly-declared copy assignment operator for a class X will have the form X& X::operator=(const X&) if each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M. Otherwise, the implicitly declared copy constructor will have the form X& X::operator=(X&) The implicitly-declared copy assignment operator for class X has the return type X&; it returns the object for which the assignment operator is invoked, that is, the object assigned to. An implicitly-declared copy assignment operator is an inline public member of its class. Because a copy assignment operator is implicitly declared for a class if not declared by the user, a base class copy assignment operator is always hidden by the copy assignment operator of a derived class. A using-declaration that brings in from a base class an assignment operator with a parameter type that could be that of a copy-assignment operator for the derived class is not considered an explicit declaration of a copy-assignment operator and does not suppress the implicit declaration of the derived class copy-assignment operator; the operator intro-duced by the using-declaration is hidden by the implicitly-declared copy-assignment operator in the derived class.

A copy assignment operator for class X is trivial if it is implicitly declared and if class X has no virtual functions and no virtual base classes, and each direct base class of X has a trivial copy assignment operator, and for all the nonstatic data members of X that are of class type (or array thereof), each such class type has a trivial copy assignment operator; otherwise the copy assignment operator is non-trivial.

An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type. A program is ill-formed if the class for which a copy assignment operator is implicitly defined has: a nonstatic data member of const type, or a nonstatic data member of reference type, or a nonstatic data member of class type (or array thereof) with an inaccessible copy assignment operator, or a base class with an inaccessible copy assignment operator. Before the implicitly-declared copy assignment operator for a class is implicitly defined, all implicitly-declared copy assignment operators for its direct base classes and its nonstatic data members shall have been implicitly defined. [Note: an implicitly-declared copy assignment operator has an exception-specification. ]

The implicitly-defined copy assignment operator for class X performs memberwise assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate nonstatic data members of X are assigned, in the order in which they were declared in the class definition. Each subobject is assigned in the manner appropriate to its type: if the subobject is of class type, the copy assignment operator for the class is used (as if by explicit qual-ification; that is, ignoring any possible virtual overriding functions in more derived classes); if the subobject is an array, each element is assigned, in the manner appropriate to the element type; if the subobject is of scalar type, the built-in assignment operator is used. It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy assignment operator.

 [Example:
struct V { };
struct A : virtual V { };
struct B : virtual V { };
struct C : B, A { };

it is unspecified whether the virtual base class subobject V is assigned twice by the implicitly-defined copy assignment operator for C. end example]

A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible. [Note: Copying one object into another using the copy constructor or the copy assignment operator does not change the layout or size of either object. ]

Whenever a temporary class object is copied using a copy constructor, and this object and the copy have the same cv-unqualified type, an implementation is permitted to treat the original and the copy as two different ways of referring to the same object and not perform a copy at all, even if the class copy constructor or destructor have side effects. For a function with a class return type, if the expression in the return statement is the name of a local object, and the cv-unqualified type of the local object is the same as the function return type, an implementation is permitted to omit creating the temporary object to hold the function return value, even if the class copy constructor or destructor has side effects. In these cases, the object is destroyed at the later of times when the original and the copy would have been destroyed without the opti-mization.

[Example:
class Thing { public: Thing();
~Thing();
Thing(const Thing&);
Thing operator=(const Thing&);
void fun();
};
Thing f() { Thing t; return t; }
Thing t2 = f(); Here t does not need to be copied when returning from f. The return value of f may be constructed
directly into the object t2. ]

START



3.v. Bsp. Konstruktor, Copy-Assigment-Konstruktor, Copy-Konstruktor


Beispiel mit Konstruktor, Copy-Kontruktor und Copy-Assignment-Konstruktor.



objects = usage_zahlen.o zahlen.o

usage_zahlen: $(objects)
	g++ -o usage_zahlen $(objects)

usage_zahlen.o: usage_zahlen.cpp zahlen.hpp
	g++ -c usage_zahlen.cpp

zahlen.o: zahlen.cpp zahlen.hpp
	g++ -c zahlen.cpp

clean:
	rm usage_zahlen $(objects)

/************************************************************************

zahlen.h - Copyright gerd

This file was generated on Mon Okt 6 2003 at 22:03:55

The original location of this file is 
/home/gerd/public_html/fh/I-PROGR3/I-PROGR3-TH/VL1/EXAMPLES/zahlen.hpp
**************************************************************************/


#ifndef ZAHLEN_HPP
#define ZAHLEN_HPP



/**
  * class zahlen
  * 
  */


class zahlen
{

/** Public methods: */

public:

  zahlen();

    /**
      * 
      * @param zahl_input
      *        Eingabe eines Zahlwertes
      */
    void input_zahl( );

    /**
      * 
      * @param show_zahl( );
      *        Ausgabe eines Zahlwertes
      */
    int show_zahl(  );



/**Attributes: */

private:
    /**
      * 
      */
    int basis;

};

#endif // ZAHLEN_HPP


/************************************************************************

zahlen.cpp - Copyright gerd

This file was generated on Mon Okt 6 2003 at 22:03:55

The original location of this file is
 /home/gerd/public_html/fh/I-PROGR3/I-PROGR3-TH/VL1/EXAMPLES/zahlen.cpp

**************************************************************************/


#include "zahlen.hpp"

#include <iostream>

using namespace std;

zahlen::zahlen()
{
  basis = 0;
  cout << "Inside class zahlen ";
}

void zahlen::input_zahl( )
{
  cout << "ENTER A NUMBER (int)  : ";
  cin >> basis;

}


int zahlen::show_zahl(  )
{
  cout << "show_zahl: BASE-NUMBER = "<< basis << endl;
}





/************************************************************************

usage_zahlen.cpp - Copyright gerd

This file was generated on Mon Okt 6 2003 at 22:15

The original location of this file is
 /home/gerd/public_html/fh/I-PROGR3/I-PROGR3-TH/VL1/EXAMPLES/usage_zahlen.cpp

**************************************************************************/

#include "zahlen.hpp"
#include <iostream>

using namespace std;

int main(){

zahlen z;

 cout << "OBJEKT z :" << endl;

 z.show_zahl();

 z.input_zahl();

 z.show_zahl();

 /******** COPY ASSIGNMENT CONSTRUCTOR ************/

 zahlen  b = z;

 cout << "OBJEKT b :" << endl;

 b.show_zahl();

 b.input_zahl();

 b.show_zahl();

 cout << "OBJEKT z :" << endl;

 z.show_zahl();

 /******** COPY CONSTRUCTOR ************/

 zahlen c(z);

 cout << "OBJEKT c :" << endl;

 c.show_zahl();

 c.input_zahl();

 c.show_zahl();

 cout << "OBJEKT z :" << endl;

 z.show_zahl();

return 1;

}


Kompilieren und Print-out des Programms:


gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL1/EXAMPLES> make
g++ -c usage_zahlen.cpp
g++ -c zahlen.cpp
g++ -o usage_zahlen usage_zahlen.o zahlen.o
gerd@kant:~/public_html/fh/I-PROGR3/I-PROGR3-TH/VL1/EXAMPLES> ./usage_zahlen
Inside class zahlen OBJEKT z :
show_zahl: BASE-NUMBER = 0
ENTER A NUMBER (int)  : 3
show_zahl: BASE-NUMBER = 3
OBJEKT b :
show_zahl: BASE-NUMBER = 3
ENTER A NUMBER (int)  : 7
show_zahl: BASE-NUMBER = 7
OBJEKT z :
show_zahl: BASE-NUMBER = 3
OBJEKT c :
show_zahl: BASE-NUMBER = 3
ENTER A NUMBER (int)  : 5
show_zahl: BASE-NUMBER = 5
OBJEKT z :
show_zahl: BASE-NUMBER = 3
  

START



4. Flache und tiefe Implementierung


Kommen in einer Klassendefinition nicht-triviale Datenstrukturen vor, also insbesondere auch Pointer, dann spricht man von einer tiefen ('deep') Struktur im Gegensatz zu einer flachen ('shallow') Struktur. Diese 'Tiefe' kommt dadurch zustande, dass Pointer auf speicherbereiche 'ausserhalb ihrer selbst' verweisen. Ein einfacher Konstruktor/ Dekonstruktor/ Copykonstructor kopiert zwar den Pointer als solchen, nicht aber den Speicherbereich, auf den er verweist. Dies muss vermieden werden. Abhilfe bieten nur explizit deklarierte und definierte Konstruktoren.


START



5. Eigene Zuweisungs-Operatoren


In C++ kann man die vorhandenen Operatoren durch eigene Definitionen erweitern, indem man für eine Klasse eine Elementfunktion mit dem Schlüsselwort operator definiert. Also operator= würde das Zeichen '=' neu definiert werden können. Dabei ist zu beachten, dass bei der Deklaration eines neuen 2-stelligen Operators nur ein (!) Argument übergeben wird,da das erste Agument das aufrufende Objekt selbst ist, dem man den 2.Paramter als zweiten Operanden übergibt.

Zu beachten ist, dass die Menge der möglichen Operatoren fest liegt; zusammen damit auch Priorität, Syntax und Auswertungsreihenfolge. Deswegen kann man mit den eigenen Definitionen die vorhandenen Definitionen nur erweitern. Ausserdem werden die Operationen, die für elementare Datentypen definiert sind, nicht automatisch auf abstrakte Datentypen übertragen.

Folgende Operatoren können nicht überladen werden:

{'.', '::', 'sizeof', '.*','?:'}

Sie sind schon für alle Objekte definiert ('.*' ist ein Zeiger auf die Komponente einer bestimmten Klasse).

Hier ein einfaches Beispiel einer Operatordefinition entnommen dem Buch von [WEISS 2000]:



  //------------------------ // //
TestIntCell.cpp // // Author: M.A.Weiss // //--------------------------

#include "DeepIntCell.hpp"
#include <iostream>

using namespace std;

int main( )
{
    IntCell m;   // Or, IntCell m( 0 );
    IntCell a(m); //Copy of m ...
    IntCell b=m;

    m.write( 5 );
    cout << "Cell contents m: " << m.read( ) << endl;

    cout << "Cell contents a as copy: " << a.read( ) << endl;
    cout << "Cell contents b as copy: " << b.read( ) << endl;


    a.write(7);
    b.write(9);

    cout << "Cell contents a on its own: " << a.read( ) << endl;
    cout << "Cell contents b on its own: " << b.read( ) << endl;

    m=b;
    a.operator=(b);

    cout << "Cell contents m after a=b: " << m.read( ) << endl;

    cout << "Cell contents a after a.operator=(b): " << a.read( ) << endl;

    return 0;
}



#ifndef DeepIntCell_HPP
#define DeepIntCell_HPP


// A class for simulating an integer memory cell with deep semantic
class IntCell
{
  public:
    explicit IntCell( int initialValue = 0 );
    IntCell(const IntCell & rhs );
    ~IntCell( );
    const IntCell & operator=(const IntCell & rhs );

    int read( ) const;
    void write( int x );

  private:
    int *storedValue;
};

#endif




//-------------------------
//
// DeepIntCell Implementation
// Author M.A.Weiss
//
//------------------------------

#include "DeepIntCell.hpp"

#include <iostream>
#if !defined( __BORLANDC__ ) || __BORLANDC__ >= 0x0530
        using namespace std;      // Borland 5.0 has a bug
#endif

IntCell::IntCell( int initialValue )
{
    storedValue = new int( initialValue );
}

IntCell::IntCell( const IntCell & rhs )
{
    storedValue = new int( *rhs.storedValue );
}

IntCell::~IntCell( )
{
    delete storedValue;
}

const IntCell & IntCell::operator=( const IntCell & rhs )
{
    if( this != &rhs )
        *storedValue = *rhs.storedValue;
    return *this;
}

int IntCell::read( ) const
{
    return *storedValue;
}

void IntCell::write( int x )
{
    *storedValue = x;
}

int f( )
{
    IntCell a( 2 );
    IntCell b = a;
    IntCell c;

    c = b;
    a.write( 4 );
    cout << a.read( ) << endl << b.read( ) << endl
         << c.read( ) << endl;
    return 0;
}


START



6. Fragen und Aufgaben


  1. Schreiben sie eine Klassendefinition für die Klasse 'person' mit den privaten Variablen 'string vorname', 'string nachname', 'int jahrgang'. Der Konstruktor person(...) soll bei Aufruf die Variablen initialisieren. Bieten Sie eine Funktion zum Lesen und Schreiben der Variablen. organisieren Sie ihren Quellkode in drei Dateien: 'use_person.cpp', 'person.hpp' sowie 'person.cpp'.


  2. Geben sie eine objektorientierte Analyse in UML samt Implementierung in C++ für folgendes Problem ('Die Mensa-Connection'): es gibt die Klienten ('Clients') auf der einen Seite, und die Mensa ('Mensa') mit bestimmten Diensten auf der anderen Seite. Mitglieder der Klienten kann man als Population ('pop') auffassen, die durch Ja-Entscheidungen ('yes()') eine Untergruppe der Entschiedenden ('dec') bilden, die die Dienste der Mensa in Anspruch nehmen wollen. Die Mensa bietet die Möglichkeit, in sie einzutreten ('enter()'). Wer eingetreten ist, bekommt den Status eines Anwesenden ('PRESENT'), der in Abhängigkeit von einer Aufenthaltszeit die Mensa wieder verlässt ('exit()'). Er wird beim Verlassen wieder Mitglied der Menge POP der Klienten. Zur UML-Analyse sollen gehören die Klassendiagramme sowie ein Interaktionsdiagramm. Zur C++-Implementierung sollen gehören die Klassenspezifikationen, die Implementierungen der Klassen sowie eine usage-Datei.


START