Post

Modern-CPP - Chapter07 - Error Handling

课程来源:b站我是龙套小果丁

  1. 见答案

  2. 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};
    };
    
  3. 见答案

  4. 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;
      }
    };
    
  5. 见答案

  6. 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.