Copy & Swap Idiom

Content

Wenn wir uns die letzte Fassung der IntegerSequence ansehen, dann fällt uns auf, dass der Kopierkonstruktor und der Zuweisungsoperator einander ähneln. Auch zwischen dem move constructor und dem move assignment gibt es Gemeinsamkeiten. In beiden mit rvalue-Referenzen arbeitenden Fällen sind alle Komponenten auszutauschen, wobei beim move constructor das “sterbende” Objekt in den Zustand gebracht wird, den auch der default constructor liefern würde.

Deswegen gibt es für C++ einen geschickten Ansatz zur Vereinfachung entsprechender Klassen, bei der die unnötige Doppelarbeit vermieden wird. Der funktioniert dahingehend, dass wir

Wir benötigen dann folgendes:

Aufgabe

Vereinfachen Sie die letzte Lösung entsprechend dem copy and swap idiom in der oben beschriebenen Form und testen Sie Ihre Lösung wieder mit valgrind und dem bereits zuvor eingesetzten Testprogramm. Vergleichen Sie die Ausgaben der Testprogramme.

#ifndef INTEGER_SEQUENCE_HPP
#define INTEGER_SEQUENCE_HPP

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

class IntegerSequence {
   public:
      IntegerSequence() : data{nullptr}, size{0}, allocated{0} {
	 std::cout << "IntegerSequence: default constructor" << std::endl;
      }
      IntegerSequence(const IntegerSequence& other) :
	    data{
	       other.data?
		  static_cast<int*>(std::malloc(other.allocated * sizeof(int)))
	       :
		  nullptr
	    }, size{other.size}, allocated{other.allocated} {
	 if (size > 0 && !data) {
	    throw std::bad_alloc();
	 }
	 for (std::size_t i = 0; i < size; ++i) {
	    data[i] = other.data[i];
	 }
	 std::cout << "IntegerSequence: copy constructor copying "
	    << size << " elements" << std::endl;
      }
      IntegerSequence(IntegerSequence&& other) :
	    data{other.data}, size{other.size}, allocated{other.allocated} {
	 other.data = nullptr; other.size = 0; other.allocated = 0;
	 std::cout << "IntegerSequence: move constructor moving "
	    << size << " elements" << std::endl;
      }
      ~IntegerSequence() {
	 std::cout << "IntegerSequence: destructor deleting "
	    << size << " elements" << std::endl;
	 std::free(data);
      }
      IntegerSequence& operator=(const IntegerSequence& other) {
	 if (other.size > allocated) {
	    int* newdata = static_cast<int*>(std::realloc(data,
	       other.allocated * sizeof(int)));
	    if (!newdata) {
	       throw std::bad_alloc();
	    }
	    data = newdata; allocated = other.allocated;
	 }
	 size = other.size;
	 for (std::size_t i = 0; i < size; ++i) {
	    data[i] = other.data[i];
	 }
	 std::cout << "IntegerSequence: assignment operator copying "
	    << size << " elements" << std::endl;
	 return *this;
      }
      IntegerSequence& operator=(IntegerSequence&& other) {
	 std::swap(data, other.data);
	 std::swap(size, other.size);
	 std::swap(allocated, other.allocated);
	 std::cout << "IntegerSequence: move assignment taking "
	    << size << " elements" << std::endl;
	 return *this;
      }

      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 <cstdlib>
#include <iostream>
#include "IntegerSequence.hpp"

void print(const IntegerSequence& is) {
   for (std::size_t i = 0; i < is.length(); ++i) {
      std::cout << " " << is(i);
   }
   std::cout << std::endl;
}

IntegerSequence gen_sequence(IntegerSequence seq, int val) {
   for (int i = 1; i <= val; ++i) {
      seq.add(i);
   }
   return seq;
}

int main() {
   IntegerSequence iseq; // default constructor
   iseq.add(1);
   IntegerSequence iseq2{iseq}; // copy constructor
   iseq2.add(2);
   iseq = iseq2; // regular assignment operator
   std::cout << "iseq: "; print(iseq);
   IntegerSequence iseq3 = gen_sequence(iseq, 3); // move constructor
   std::cout << "iseq3: "; print(iseq3);
   IntegerSequence iseq4;
   iseq4 = gen_sequence(iseq, 3); // move assignment
   std::cout << "iseq4: "; print(iseq4);
}