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$