Verwaltung von Ressourcen durch Objekte

Content

Da das Auf- und Abbauen von Objekten wohldefiniert erfolgt, liegt es nahe, die Verwaltung von Ressourcen mit dem Lebenszyklus eines Objekts zu verknüpfen, in dem Sinne, dass

Dieses Konzept ist unter dem Akronym RAII (resource acquisition is initialization) bekannt geworden. Ressourcen können dabei recht vielfältig sein:

Diese Vorgehensweise bietet zwei wichtige Vorteile:

Wenn die Objekte einer Klasse Ressourcen verwalten, müssen wir jedoch darauf achten, dass diese bzw. die Verweise darauf nicht unbeabsichtigt durch Kopieraktionen geklont werden. Würde dies geschehen, käme es zu mehrfachen Freigaben -- bei einer Freigabe belegten Speichers auf dem Heap kann dies katastrophale Konsequenzen haben.

The Rule of Three

Bei Klassen von Objekten, die Ressourcen verwalten, sind folgende spezielle Methoden immer ohne Ausnahme explizit zu definieren bzw. mit Hilfe von delete zu deaktivieren, da die vom Übersetzer voreingestellten Fassungen zu unerwünschten Klonen führen:

Aufgabe

Gegeben sei folgende Klasse, die eine Sequenz ganzer Zahlen verwaltet. Die Sequenz ist zu Beginn leer und mit Hilfe der Methode add können sukzessive Zahlen hinzugefügt werden. Damit der indizierte elementweise Zugriff mit dem Funktionsoperator immer effizient (d.h. mit konstantem Aufwand) möglich ist, werden alle Elemente der Sequenz immer in einem zusammenhängenden Speicherbereich auf dem Heap verwaltet. Einige Anmerkungen zum weiteren Verständnis dieser Klasse:

Passen Sie die weiter unten angegebene Vorlage entsprechend der Rule of Three an und ergänzen Sie das Testprogramm so, dass sowohl der Kopierkonstruktor als auch der Zuweisungsoperator zum Zuge kommt. Überprüfen Sie jeweils die Korrektheit mit dem Werkzeug valgrind.

Hinweise: Mit std::malloc können Sie Speicher auf dem Heap belegen. Anzugeben ist die Zahl der benötigten Bytes (analog zum zweiten Parameter von std::realloc). Speicher, der mit std::malloc oder std::realloc belegt worden ist, sollte mit std::free freigegeben werden.

Vorlage

#ifndef INTEGER_SEQUENCE_HPP
#define INTEGER_SEQUENCE_HPP

#include <cassert>
#include <cstdlib>
#include <new>

class IntegerSequence {
   public:
      IntegerSequence() : data(nullptr), size(0), allocated(0) {
      }
      void add(int value) {
	 if (size == allocated) {
	    std::size_t newsize = allocated * 2 + 8;
	    int* newdata = static_cast<int*>(std::realloc(data,
	       newsize * sizeof(int)));
	    if (!newdata) {
	       throw std::bad_alloc();
	    }
	    data = newdata;
	    allocated = newsize;
	 }
	 data[size++] = value;
      }
      std::size_t length() const {
	 return size;
      }
      int& operator()(std::size_t index) {
	 assert(index < size);
	 return data[index];
      }
      const int& operator()(std::size_t index) const {
	 assert(index < size);
	 return data[index];
      }
   private:
      int* data;
      std::size_t size;
      std::size_t allocated;
};

#endif
#include <iostream>
#include "IntegerSequence.hpp"

int main() {
   IntegerSequence iseq;
   int value;
   while (std::cin >> value) {
      iseq.add(value);
   }
   for (std::size_t i = 0; i < iseq.length(); ++i) {
      std::cout << " " << iseq(i);
   }
   std::cout << std::endl;
}

Die Vorlage ist in dieser Form übersetzbar und lauffähig, aber valgrind berichtet bereits, dass ein Speicherleck vorliegt:

theon$ g++ -Wall -o test_is test_is.cpp
theon$ echo 1 2 3 | valgrind ./test_is
==7065== Memcheck, a memory error detector
==7065== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7065== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==7065== Command: ./test_is
==7065== 
 1 2 3
==7065== 
==7065== HEAP SUMMARY:
==7065==     in use at exit: 10,288 bytes in 3 blocks
==7065==   total heap usage: 4 allocs, 1 frees, 82,992 bytes allocated
==7065== 
==7065== LEAK SUMMARY:
==7065==    definitely lost: 32 bytes in 1 blocks
==7065==    indirectly lost: 0 bytes in 0 blocks
==7065==      possibly lost: 10,256 bytes in 2 blocks
==7065==    still reachable: 0 bytes in 0 blocks
==7065==         suppressed: 0 bytes in 0 blocks
==7065== Rerun with --leak-check=full to see details of leaked memory
==7065== 
==7065== For counts of detected and suppressed errors, rerun with: -v
==7065== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
theon$ 

Beachten Sie hier die folgende Zeile: definitely lost: 32 bytes in 1 blocks. Auf unserer Architektur ist sizeof(int) == 4 und zu Beginn werden 8 int angelegt, so dass es sich um 32 Bytes handelt.