Beispiellösung

Content

#ifndef ARRAY_HPP
#define ARRAY_HPP

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <initializer_list>
#include <iterator>
#include <new>
#include <utility>

template<typename T>
class Array {
   private:
      using Tag = enum {AllocateStorage};
      Array(Tag tag, std::size_t size) :
	 size(size),
	 data(size > 0?
	       static_cast<T*>(operator new(sizeof(T) * size))
	    :
	       nullptr) {
      }
   public:
      Array() : size(0), data(nullptr) {
      }
      Array(std::size_t size) : Array(AllocateStorage, size) {
	 for (std::size_t index = 0; index < size; ++index) {
	    new (data + index) T();
	 }
      }
      Array(std::size_t size, const T& t) : Array(AllocateStorage, size) {
	 for (std::size_t index = 0; index < size; ++index) {
	    new (data + index) T(t);
	 }
      }
      template<typename Iterator>
      Array(Iterator it1, Iterator it2) :
	    Array(AllocateStorage, std::distance(it1, it2)) {
	 std::size_t index = 0;
	 while (it1 != it2 && index < size) {
	    new (data + index++) T(*it1++);
	 }
	 size = index; // just in case
      }
      Array(std::initializer_list<T> elements) :
	    Array(elements.begin(), elements.end()) {
      }
      Array(const Array& other) : Array(AllocateStorage, other.size) {
	 for (std::size_t index = 0; index < size; ++index) {
	    new (data + index) T(other.data[index]);
	 }
      }
      friend void swap(Array& a1, Array& a2) {
	 using std::swap;
	 swap(a1.size, a2.size);
	 swap(a1.data, a2.data);
      }
      Array(Array&& other) : Array() {
	 swap(*this, other);
      }
      ~Array() {
	 for (std::size_t index = 0; index < size; ++index) {
	    data[index].~T();
	 }
	 operator delete(data);
      }
      Array& operator=(Array other) {
	 swap(*this, other);
	 return *this;
      }
      std::size_t get_size() const {
	 return size;
      }
      T& operator()(std::size_t index) {
	 assert(index < size);
	 return data[index];
      }
      const T& operator()(std::size_t index) const {
	 assert(index < size);
	 return data[index];
      }
      T* begin() {
	 return data;
      }
      const T* begin() const {
	 return data;
      }
      T* end() {
	 return data + size;
      }
      const T* end() const {
	 return data + size;
      }
   private:
      std::size_t size;
      T* data;
};

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

int main() {
   Array<int> a = {2, 3, 5, 7, 11, 13};
   for (auto val: a) {
      std::cout << " " << val;
   }
   std::cout << std::endl;
}
theon$ g++ -Wall -o test1 test1.cpp
theon$ valgrind ./test1
==2334== Memcheck, a memory error detector
==2334== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2334== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2334== Command: ./test1
==2334== 
 2 3 5 7 11 13
==2334== 
==2334== HEAP SUMMARY:
==2334==     in use at exit: 5,128 bytes in 1 blocks
==2334==   total heap usage: 3 allocs, 2 frees, 77,856 bytes allocated
==2334== 
==2334== LEAK SUMMARY:
==2334==    definitely lost: 0 bytes in 0 blocks
==2334==    indirectly lost: 0 bytes in 0 blocks
==2334==      possibly lost: 5,128 bytes in 1 blocks
==2334==    still reachable: 0 bytes in 0 blocks
==2334==         suppressed: 0 bytes in 0 blocks
==2334== Rerun with --leak-check=full to see details of leaked memory
==2334== 
==2334== For counts of detected and suppressed errors, rerun with: -v
==2334== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
theon$ 

Der zweidimensionale Fall funktioniert auf Anhieb:

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

int main() {
   Array<Array<int>> m = {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9}
   };
   for (auto& row: m) {
      for (auto val: row) {
	 std::cout << " " << val;
      }
      std::cout << std::endl;
   }
}
theon$ g++ -Wall -o test2 test2.cpp
theon$ valgrind ./test2
==2363== Memcheck, a memory error detector
==2363== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2363== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2363== Command: ./test2
==2363== 
 1 2 3
 4 5 6
 7 8 9
==2363== 
==2363== HEAP SUMMARY:
==2363==     in use at exit: 5,128 bytes in 1 blocks
==2363==   total heap usage: 9 allocs, 8 frees, 77,952 bytes allocated
==2363== 
==2363== LEAK SUMMARY:
==2363==    definitely lost: 0 bytes in 0 blocks
==2363==    indirectly lost: 0 bytes in 0 blocks
==2363==      possibly lost: 5,128 bytes in 1 blocks
==2363==    still reachable: 0 bytes in 0 blocks
==2363==         suppressed: 0 bytes in 0 blocks
==2363== Rerun with --leak-check=full to see details of leaked memory
==2363== 
==2363== For counts of detected and suppressed errors, rerun with: -v
==2363== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
theon$ 

Zu den Vor- und Nachteilen

Knacknuss

Leider werden die Zeilen zuerst erzeugt, dann kopierkonstruiert und danach wird die originale Kopie weggeworfen, obwohl ein move constructor angeboten wird. Warum?

Das Problem ist, dass eine std::initializer_list ihre Inhalte nur als const zur Verfügung stellt -- das macht eine Übergabe per move constructor unmöglich. Das liegt daran, dass solche Initialisierungslisten durch den Übersetzer häufig in einem Speicherbereich mit Schreibschutz angelegt werden, so dass sie mehrfach genutzt werden können. Bei einer zwei-dimensionalen Matrix werden jedoch zunächst die Zeilen-Arrays in temporären Objekten konstruiert, die durchaus verschoben werden könnten. Dies geht jedoch leider nicht, selbst wenn std::move hinzugefügt wird.

Historisch liegt das daran, dass std::initializer_list in einer Zeit entwickelt worden ist, in der die move-Semantik noch nicht umgesetzt war und dieser Sonderfall keine Berücksichtigung fand. Zwar gab es später einen Korrekturvorschlag von David Kraus (N4166 und P0065), aber diese Vorschläge sind bislang nicht in den Standard übernommen worden.

Es gibt nicht-triviale Workarounds, einer findet sich im Blog von Sumant Tambe, der das Problem mit einer Hilfsklasse löst, die lvalues von rvalues unterscheiden kann und, wenn ohne Gefahr möglich, das const-Attribut wegkonvertiert.