Modern-CPP - Chapter07 - Error Handling
课程来源:b站我是龙套小果丁
见答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
#include <cstddef> #include <new> template <typename T, std::size_t N> class InplaceVector { public: InplaceVector() = default; ~InplaceVector() { for (auto ptr = Data(), end = ptr + size_; ptr < end; ptr++) { ptr->~T(); } } T *Data() { return reinterpret_cast<T *>(buffer_); } const T *Data() const { return reinterpret_cast<const T *>(buffer_); } std::size_t Size() const noexcept { return size_; } std::size_t Capacity() const noexcept { return N; } bool Empty() const noexcept { return size_ == 0; } void PushBack(const T &elem) { if (size_ >= N) { throw std::bad_alloc{}; } auto newPos = Data() + size_; new (newPos) T{elem}; size_++; } void PopBack() { if (Empty()) { throw std::out_of_range{"Cannot pop from empty vector"}; } auto currPos = Data() + (size_ - 1); currPos->~T(); size_--; } T &operator[](std::size_t idx) { return *(Data() + idx); } const T &operator[](std::size_t idx) const { return *(Data() + idx); } T &at(std::size_t idx) { if (idx >= size_) { throw std::out_of_range{"Index out of range"}; } return *(Data() + idx); } const T &at(std::size_t idx) const { if (idx >= size_) { throw std::out_of_range{"Index out of range"}; } return *(Data() + idx); } private: alignas(T) std::byte buffer_[sizeof(T) * N]; std::size_t size_{0}; };
见答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
#include <algorithm> template <typename T> struct ListNode { ListNode *prev; ListNode *next; T val; }; template <typename T> class ListBase { protected: ListNode<T> sentinel_; ListBase() : sentinel_(&sentinel_, &sentinel_) {} ListNode<T> &GetSentinel_() { return sentinel_; } const ListNode<T> &GetSentinel_() const { return sentinel_; } void ClearNodes_() { ListNode<T> *current = sentinel_.next; while (current != &sentinel_) { ListNode<T> *next = current->next; delete current; current = next; } // Reset sentinel pointers sentinel_.next = &sentinel_; sentinel_.prev = &sentinel_; } ~ListBase() { ClearNodes_(); } }; template <typename T> class List : protected ListBase<T> { using Base = ListBase<T>; public: class ConstIterator { const ListNode<T> *node_; public: ConstIterator(const ListNode<T> *node) : node_{node} {} ConstIterator operator++(int) noexcept { auto node0 = node_; node_ = node_->next; return ConstIterator{node0}; } ConstIterator &operator++() noexcept { node_ = node_->next; return *this; } const T &operator*() const noexcept { return node_->val; } const T *operator->() const noexcept { return &(node_->val); } bool operator==(const ConstIterator &another) const noexcept = default; }; List() = default; template <typename It> List(It begin, It end) { // Guard class to ensure cleanup on exception class Guard { List &list_; bool released_ = false; public: explicit Guard(List &list) : list_(list) {} ~Guard() { if (!released_) { // Clean up any allocated nodes if construction fails list_.Base::ClearNodes_(); } } void Release() { released_ = true; } }; Guard guard{*this}; ListNode<T> *curr = &this->GetSentinel_(); for (auto it = begin; it != end; ++it) { ListNode<T> *new_node = new ListNode<T>{curr, &this->GetSentinel_(), *it}; curr->next = new_node; this->GetSentinel_().prev = new_node; curr = new_node; } guard.Release(); } auto begin() const { return ConstIterator{this->sentinel_.next}; } auto end() const { return ConstIterator{&this->sentinel_}; } List(const List &another) : List{another.begin(), another.end()} {} void swap(List &other) noexcept { if (this == &other) return; ListNode<T> &this_sentinel = this->GetSentinel_(); ListNode<T> &other_sentinel = other.GetSentinel_(); bool this_empty = (this_sentinel.next == &this_sentinel); bool other_empty = (other_sentinel.next == &other_sentinel); if (this_empty && other_empty) { // Both empty, nothing to do return; } if (this_empty) { // This is empty, other is not this_sentinel.next = other_sentinel.next; this_sentinel.prev = other_sentinel.prev; this_sentinel.next->prev = &this_sentinel; this_sentinel.prev->next = &this_sentinel; // Make other empty other_sentinel.next = &other_sentinel; other_sentinel.prev = &other_sentinel; } else if (other_empty) { // Other is empty, this is not other_sentinel.next = this_sentinel.next; other_sentinel.prev = this_sentinel.prev; other_sentinel.next->prev = &other_sentinel; other_sentinel.prev->next = &other_sentinel; // Make this empty this_sentinel.next = &this_sentinel; this_sentinel.prev = &this_sentinel; } else { // Both non-empty, swap their pointers std::swap(this_sentinel.next, other_sentinel.next); std::swap(this_sentinel.prev, other_sentinel.prev); // Fix the node pointers this_sentinel.next->prev = &this_sentinel; this_sentinel.prev->next = &this_sentinel; other_sentinel.next->prev = &other_sentinel; other_sentinel.prev->next = &other_sentinel; } } List &operator=(const List &another) { if (this != &another) { List temp(another); swap(temp); } return *this; } };
见答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
#include <catch2/catch_test_macros.hpp> #include "list.hpp" class SomeClassMayThrow { private: int val_; // Use static variables to record call count static inline int constructCount = 0; static inline int destructCount = 0; static inline int copyCount = 0; // Make copyThreshold a separate variable which can be set in different test cases static inline int copyThreshold = 0; public: SomeClassMayThrow() : val_{0} {} SomeClassMayThrow(int val) : val_{val} {} SomeClassMayThrow(const SomeClassMayThrow &another) : val_{another.val_} { if (copyCount++ == copyThreshold) { throw std::runtime_error{"Test exception"}; } constructCount++; } ~SomeClassMayThrow() { destructCount++; } auto GetVal() const noexcept { return val_; } static int GetConstructCount() { return constructCount; } static int GetDestructCount() { return destructCount; } static int GetCopyCount() { return copyCount; } static void ResetCounters() { constructCount = 0; destructCount = 0; copyCount = 0; } static void SetCopyThreshold(int threshold) { copyThreshold = threshold; } }; TEST_CASE("Functionality test", "[functionality]") { std::vector<int> v{1, 2, 3}; List<int> l{v.begin(), v.end()}; auto it = l.begin(); REQUIRE(*it == 1); ++it; REQUIRE(*it == 2); ++it; REQUIRE(*it == 3); ++it; REQUIRE(it == l.end()); } TEST_CASE("Basic exception safety test", "[basic]") { SomeClassMayThrow::SetCopyThreshold(2); SomeClassMayThrow::ResetCounters(); bool exceptionCaught = false; { // Wrap the test in curly braces so that the vector goes out of scope before test terminates std::vector<SomeClassMayThrow> a; a.reserve(3); a.emplace_back(1); a.emplace_back(2); a.emplace_back(3); try { List<SomeClassMayThrow> l{a.begin(), a.end()}; } catch (const std::exception &ex) { exceptionCaught = true; INFO("Exception caught as expected: " << ex.what()); } } REQUIRE(exceptionCaught); REQUIRE(SomeClassMayThrow::GetConstructCount() == 2); REQUIRE(SomeClassMayThrow::GetDestructCount() == 6); } TEST_CASE("Strong exception safety test", "[strong]") { SomeClassMayThrow::SetCopyThreshold(5); SomeClassMayThrow::ResetCounters(); std::vector<SomeClassMayThrow> a; a.reserve(3); a.emplace_back(1); a.emplace_back(2); a.emplace_back(3); List<SomeClassMayThrow> l{a.begin(), a.end()}; List<SomeClassMayThrow> l2{a.begin() + 1, a.end()}; // Before assignment, store the values in l2 for later comparison std::vector<int> originalValues; for (auto it = l2.begin(); it != l2.end(); ++it) { originalValues.push_back(it->GetVal()); } // Verify original values in l2 (should be 2, 3) REQUIRE(originalValues.size() == 2); REQUIRE(originalValues[0] == 2); REQUIRE(originalValues[1] == 3); bool exceptionCaught = false; try { l2 = l; INFO("Assignment completed without exception"); } catch (const std::exception &ex) { exceptionCaught = true; INFO("Exception caught as expected: " << ex.what()); // Check if l2 values are unchanged std::vector<int> postExceptionValues; for (auto it = l2.begin(); it != l2.end(); ++it) { postExceptionValues.push_back(it->GetVal()); INFO("Value in l2 after failed assignment: " << it->GetVal()); } // Verify l2 still has the same values as before (2, 3) REQUIRE(postExceptionValues.size() == originalValues.size()); for (size_t i = 0; i < originalValues.size(); ++i) { REQUIRE(postExceptionValues[i] == originalValues[i]); } } REQUIRE(exceptionCaught); }
This post is licensed under CC BY 4.0 by the author.