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() noexcept : 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) noexcept {
	 using std::swap;
	 swap(a1.size, a2.size);
	 swap(a1.data, a2.data);
      }
      Array(Array&& other) noexcept : Array() {
	 swap(*this, other);
      }
      ~Array() {
	 for (std::size_t index = 0; index < size; ++index) {
	    data[index].~T();
	 }
	 operator delete(data);
      }
      Array& operator=(Array other) noexcept {
	 swap(*this, other);
	 return *this;
      }
      std::size_t get_size() const noexcept {
	 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() noexcept {
	 return data;
      }
      const T* begin() const noexcept {
	 return data;
      }
      T* end() noexcept {
	 return data + size;
      }
      const T* end() const noexcept {
	 return data + size;
      }
   private:
      std::size_t size;
      T* data;
};

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

template<typename T>
void test_operation(unsigned int test, std::string description,
      T&& operation) {
   std::cout << "Test " << std::setw(2) << std::right << test << ": ";
   std::cout << std::setw(30) << std::left << std::move(description) << " ";
   try {
      operation();
      std::cout << "ok" << std::endl;
   } catch (std::exception& e) {
      std::cout << "exception: " << e.what() << std::endl;
   } catch (...) {
      std::cout << "unknown exception" << std::endl;
   }
}

struct FailingObject {
   class Exception: public std::exception {
      const char* what() const noexcept override {
	 return "FailingObject exception";
      }
   };
   FailingObject() {
      throw Exception();
   }
   FailingObject(const FailingObject& other) {
      throw Exception();
   }
   FailingObject(int i) {
      if (i < 0) throw Exception();
   }
   FailingObject& operator=(const FailingObject& other) {
      throw Exception();
   }
};

int main() {
   unsigned int test = 0;
   test_operation(++test, "Array()", []() { Array<FailingObject>(); });
   test_operation(++test, "Array(0)", []() { Array<FailingObject>(0); });
   test_operation(++test, "Array(1)", []() { Array<FailingObject>(1); });
   test_operation(++test, "Array(1, FailingObject(1))",
      []() { Array<FailingObject>(1, FailingObject(1)); });
   test_operation(++test, "Array(it1, it2) from {1, 2}", []() {
      int values[] = {1, 2, 3};
      Array<FailingObject>(values, values + sizeof(values)/sizeof(values[0]));
   });
   test_operation(++test, "Array(it1, it2) from {1, -2}", []() {
      int values[] = {1, -2};
      Array<FailingObject>(values, values + sizeof(values)/sizeof(values[0]));
   });
   test_operation(++test, "Array{1, 2}", []() {
      Array<FailingObject>{1, 2};
   });
   test_operation(++test, "Array{1, -2}", []() {
      Array<FailingObject>{1, -2};
   });
   test_operation(++test, "Array copy constructor", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject> array2(array);
   });
   test_operation(++test, "Array move constructor", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject>(std::move(array));
   });
   test_operation(++test, "Array swap", []() {
      int values[] = {1, 2};
      Array<FailingObject> array1(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject> array2(values,
	 values + sizeof(values)/sizeof(values[0]));
      using std::swap;
      swap(array1, array2);
   });
   test_operation(++test, "Array assignment", []() {
      int values[] = {1, 2};
      Array<FailingObject> array1(values,
	 values + sizeof(values)/sizeof(values[0]));
      Array<FailingObject> array2;
      array2 = std::move(array1);
   });
   test_operation(++test, "Array get_size", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      array.get_size();
   });
   test_operation(++test, "Array access by index", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      array(1);
   });
   test_operation(++test, "Array access by index const", []() {
      int values[] = {1, 2};
      const Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      array(1);
   });
   test_operation(++test, "Array iterators", []() {
      int values[] = {1, 2};
      Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      for (auto& object: array) {
      }
   });
   test_operation(++test, "Array const iterators", []() {
      int values[] = {1, 2};
      const Array<FailingObject> array(values,
	 values + sizeof(values)/sizeof(values[0]));
      for (auto& object: array) {
      }
   });
   std::cout << test << " tests survived." << std::endl;
}
theon$ g++ --coverage -Wall -Wno-unused-variable -o testit testit.cpp
theon$ ./testit
Test  1: Array()                        ok
Test  2: Array(0)                       ok
Test  3: Array(1)                       exception: FailingObject exception
Test  4: Array(1, FailingObject(1))     exception: FailingObject exception
Test  5: Array(it1, it2) from {1, 2}    ok
Test  6: Array(it1, it2) from {1, -2}   exception: FailingObject exception
Test  7: Array{1, 2}                    exception: FailingObject exception
Test  8: Array{1, -2}                   exception: FailingObject exception
Test  9: Array copy constructor         exception: FailingObject exception
Test 10: Array move constructor         ok
Test 11: Array swap                     ok
Test 12: Array assignment               ok
Test 13: Array get_size                 ok
Test 14: Array access by index          ok
Test 15: Array access by index const    ok
Test 16: Array iterators                ok
Test 17: Array const iterators          ok
17 tests survived.
theon$ gcov testit
File 'testit.cpp'
Lines executed:93.33% of 90
Creating 'testit.cpp.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/iostream'
Lines executed:100.00% of 1
Creating 'iostream.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/stl_iterator_base_funcs.h'
Lines executed:100.00% of 5
Creating 'stl_iterator_base_funcs.h.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/stl_iterator_base_types.h'
Lines executed:100.00% of 2
Creating 'stl_iterator_base_types.h.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/move.h'
Lines executed:100.00% of 7
Creating 'move.h.gcov'

File 'array.hpp'
Lines executed:94.83% of 58
Creating 'array.hpp.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/initializer_list'
Lines executed:100.00% of 3
Creating 'initializer_list.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/iomanip'
Lines executed:100.00% of 2
Creating 'iomanip.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/ios_base.h'
Lines executed:100.00% of 21
Creating 'ios_base.h.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/x86_64-pc-solaris2.11/bits/gthr-default.h'
Lines executed:0.00% of 3
Creating 'gthr-default.h.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/new'
Lines executed:100.00% of 3
Creating 'new.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/exception.h'
Lines executed:100.00% of 1
Creating 'exception.h.gcov'
theon$ cat array.hpp.gcov
        -:    0:Source:array.hpp
        -:    0:Graph:testit.gcno
        -:    0:Data:testit.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#ifndef ARRAY_HPP
        -:    2:#define ARRAY_HPP
        -:    3:
        -:    4:#include 
        -:    5:#include 
        -:    6:#include 
        -:    7:#include 
        -:    8:#include 
        -:    9:#include 
        -:   10:#include 
        -:   11:
        -:   12:template
        -:   13:class Array {
        -:   14:   private:
        -:   15:      using Tag = enum {AllocateStorage};
       17:   16:      Array(Tag tag, std::size_t size) :
        -:   17:	 size(size),
       17:   18:	 data(size > 0?
        -:   19:	       static_cast(operator new(sizeof(T) * size))
        -:   20:	    :
       34:   21:	       nullptr) {
       17:   22:      }
        -:   23:   public:
        4:   24:      Array() noexcept : size(0), data(nullptr) {
        4:   25:      }
        3:   26:      Array(std::size_t size) : Array(AllocateStorage, size) {
        2:   27:	 for (std::size_t index = 0; index < size; ++index) {
        2:   28:	    new (data + index) T();
        -:   29:	 }
        1:   30:      }
        2:   31:      Array(std::size_t size, const T& t) : Array(AllocateStorage, size) {
        1:   32:	 for (std::size_t index = 0; index < size; ++index) {
        2:   33:	    new (data + index) T(t);
        -:   34:	 }
    #####:   35:      }
        -:   36:      template
       13:   37:      Array(Iterator it1, Iterator it2) :
       15:   38:	    Array(AllocateStorage, std::distance(it1, it2)) {
       13:   39:	 std::size_t index = 0;
       61:   40:	 while (it1 != it2 && index < size) {
       28:   41:	    new (data + index++) T(*it1++);
        -:   42:	 }
       11:   43:	 size = index; // just in case
       11:   44:      }
        1:   45:      Array(std::initializer_list elements) :
        1:   46:	    Array(elements.begin(), elements.end()) {
    #####:   47:      }
        2:   48:      Array(const Array& other) : Array(AllocateStorage, other.size) {
        1:   49:	 for (std::size_t index = 0; index < size; ++index) {
        2:   50:	    new (data + index) T(other.data[index]);
        -:   51:	 }
    #####:   52:      }
        4:   53:      friend void swap(Array& a1, Array& a2) noexcept {
        -:   54:	 using std::swap;
        4:   55:	 swap(a1.size, a2.size);
        4:   56:	 swap(a1.data, a2.data);
        4:   57:      }
        2:   58:      Array(Array&& other) noexcept : Array() {
        2:   59:	 swap(*this, other);
        2:   60:      }
       21:   61:      ~Array() {
       21:   62:	 for (std::size_t index = 0; index < size; ++index) {
        -:   63:	    data[index].~T();
        -:   64:	 }
       21:   65:	 operator delete(data);
       21:   66:      }
        1:   67:      Array& operator=(Array other) noexcept {
        1:   68:	 swap(*this, other);
        1:   69:	 return *this;
        -:   70:      }
        1:   71:      std::size_t get_size() const noexcept {
        1:   72:	 return size;
        -:   73:      }
        1:   74:      T& operator()(std::size_t index) {
        1:   75:	 assert(index < size);
        1:   76:	 return data[index];
        -:   77:      }
        1:   78:      const T& operator()(std::size_t index) const {
        1:   79:	 assert(index < size);
        1:   80:	 return data[index];
        -:   81:      }
        1:   82:      T* begin() noexcept {
        1:   83:	 return data;
        -:   84:      }
        1:   85:      const T* begin() const noexcept {
        1:   86:	 return data;
        -:   87:      }
        1:   88:      T* end() noexcept {
        1:   89:	 return data + size;
        -:   90:      }
        1:   91:      const T* end() const noexcept {
        1:   92:	 return data + size;
        -:   93:      }
        -:   94:   private:
        -:   95:      std::size_t size;
        -:   96:      T* data;
        -:   97:};
        -:   98:
        -:   99:#endif
theon$ rm -f *.gcov
theon$ 

Was wäre nun gewesen, wenn wir eine Methode mit noexcept deklariert hätten, bei der das nicht zulässig gewesen wäre?

theon$ diff -U 2 array.hpp array-debug.hpp
--- array.hpp	Thu Jun 28 13:17:38 2018
+++ array-debug.hpp	Thu Jun 28 14:11:34 2018
@@ -24,5 +24,5 @@
       Array() noexcept : size(0), data(nullptr) {
       }
-      Array(std::size_t size) : Array(AllocateStorage, size) {
+      Array(std::size_t size) noexcept : Array(AllocateStorage, size) {
 	 for (std::size_t index = 0; index < size; ++index) {
 	    new (data + index) T();
theon$ diff -U 2 testit.cpp testit-debug.cpp
--- testit.cpp	Thu Jun 28 14:09:57 2018
+++ testit-debug.cpp	Thu Jun 28 14:11:38 2018
@@ -2,5 +2,5 @@
 #include 
 #include 
-#include "array.hpp"
+#include "array-debug.hpp"
 
 template
theon$ g++ -o testit-debug testit-debug.cpp
theon$ ./testit-debug
Test  1: Array()                        ok
Test  2: Array(0)                       ok
terminate called after throwing an instance of 'FailingObject::Exception'
  what():  FailingObject exception
Test  3: Array(1)                       theon$ rm -f core
theon$