實現一個簡單的 std::unique_ptr

フランドール·スカーレット發表於2024-03-10

實現一個簡單的 std::unique_ptr

簡介

std::unique_ptr 是一個獨佔資源所有權的智慧指標,透過 RAII 來自動管理資源的構造和析構。

在標準庫中,std::unique_ptr 的通常實現是具有空基類最佳化。具體來講,對於 std::unique_ptr 的刪除器是其型別中的一部分,如果沒有空基類最佳化,那麼 std::unique_ptr 所佔用的空間將包括至少一個空類的大小。不過這裡只是簡單實現一個 unique_ptr ,不涉及空基類最佳化的具體實現。

unique_ptr 擁有對資源物件的獨佔所有權,這意味著只能有一個 unique_ptr 擁有資源的所有權(換句話說,一個資源的管理只能被一個 unique_ptr 託管,多個是不被允許的,但透過一些不合理的行為可以讓多個例項共同管理一個)。因為需要確保唯一擁有物件的所有權,因此需要將其複製建構函式顯示棄置,需要複製構造的一個更合理的設計是由類自己提供一個可以複製的函式,返回一個 unique_ptr 。不過,可以透過移動的方式將資源的所有權轉交給另一個 unique_ptr ,或者是自己來接管資源。

class Foo {
public:
  auto Clone() const -> std::unique_ptr<Foo>;
};

要使用 std::unique_ptr ,可以透過直接建立一個 std::unique_ptr 物件,也可以使用 std::make_unique 進行構造。前者的好處是可以自定義刪除器,若不需要自定義刪除器,則最好使用 std::make_unique 。對於 auto t = std::unique_ptr<T>(new T()) ,這個過程不是異常安全的,當發生異常時,可能導致申請的資源沒有被 std::unique_ptr 託管,從而發生記憶體洩露,而使用 std::make_unique 則不會。

實現一個 unique_ptr

對於一個 unique_ptr ,我們只需要讓它能夠支援移動構造和移動賦值,並需要將複製構造和複製複製刪除,在析構時將資源回收,然後支援釋放指標和獲取指標,並過載一些裸指標的訪問行為即可。要注意的是,需要使用一定的方法,保證刪除器是一個可被呼叫的型別,即使使用者實現的無法釋放資源,這裡使用 c++20concept 約束模板。

由於這裡的實現並沒有實現空基類最佳化,因此需要將裸指標和刪除器物件作為成員。

template <typename Ty, typename Deleter = std::default_delete<Ty>>
  requires std::is_invocable_v<Deleter, Ty *>
class UniquePtr {
public:
  constexpr UniquePtr() noexcept : ptr_(nullptr), deleter_() {}

  constexpr UniquePtr(std::nullptr_t) noexcept : UniquePtr() {}

  explicit UniquePtr(Ty *ptr) noexcept : ptr_(ptr), deleter_() {}

  explicit UniquePtr(Ty *ptr, Deleter &&deleter) noexcept
      : ptr_(ptr), deleter_(std::move(deleter)) {}

  explicit UniquePtr(Ty *ptr, const Deleter &deleter) noexcept
      : ptr_(ptr), deleter_(deleter) {}

  UniquePtr(const UniquePtr &) = delete;

  auto operator=(const UniquePtr &) -> UniquePtr & = delete;

  UniquePtr(UniquePtr &&) noexcept = default;

  auto operator=(UniquePtr &&) noexcept -> UniquePtr & = default;

  ~UniquePtr() noexcept {
    if (this->ptr_ != nullptr) {
      this->deleter_(std::move(this->ptr_));
      this->ptr_ = nullptr;
    }
  }

  auto Release() noexcept -> Ty * {
    this->ptr_ = nullptr;
    return this->ptr_;
  }

  auto Reset(Ty *ptr = nullptr) noexcept -> void {
    if (this->ptr_ != nullptr) {
      this->deleter_(this->ptr_);
    }
    this->ptr_ = ptr;
  }

  auto Swap(UniquePtr &other) noexcept -> void {
    std::swap(this->ptr_, other.ptr_);
    std::swap(this->deleter_, other.deleter_);
  }

  auto Get() const noexcept -> Ty * { return this->ptr_; }

  auto GetDeleter() noexcept -> Deleter & { return this->deleter_; }

  auto GetDeleter() const noexcept -> const Deleter & { return this->deleter_; }

  explicit operator bool() const noexcept { return this->ptr_ != nullptr; }

  auto operator*() const noexcept -> Ty & { return *this->ptr_; }

  auto operator->() const noexcept -> Ty * { return this->ptr_; }

private:
  Ty *ptr_;
  Deleter deleter_;
};

接著,還有 make_unique 需要被實現。對於 make_unique 來說,它需要將傳入的引數轉發給建構函式,這裡也需要約束一下。

template <typename Ty, typename... Args>
  requires requires(Args &&...args) { new Ty(std::forward<Args>(args)...); }
inline constexpr auto MakeUnique(Args &&...args) -> UniquePtr<Ty> {
  return UniquePtr<Ty>(new Ty(std::forward<Args>(args)...));
}

還有一個 std::unique_ptr<T[]> 的過載版本,大體是類似的,這裡就不實現程式碼了。

相關文章