std::unique_ptr — это реализация интеллектуального указателя на C++, которая обеспечивает монопольное владение объектом, а это означает, что объект, управляемый unique_ptr, может принадлежать только одному экземпляру unique_ptr.

Вот реализация std::unique_ptr на C++:

template <typename T>
struct default_delete {
  default_delete() = default;
  default_delete(const default_delete&) = default;

  template <typename U>
  default_delete(const default_delete<U>&) {} 

  void operator()(T* ptr) const { delete ptr; }
};

template <typename T, typename Deleter = default_delete<T>>
class unique_ptr {
 public:
  unique_ptr() = default;
  unique_ptr(T* ptr) : ptr_(ptr) {}
  unique_ptr(T* ptr, const Deleter& deleter) : ptr_(ptr), deleter_(deleter) {}

  ~unique_ptr() { deleter_(ptr_); }

  unique_ptr(const unique_ptr& other) = delete;

  unique_ptr(unique_ptr&& other) noexcept
      : ptr_(other.release()), deleter_(other.deleter_) {}
  
  // generalized move ctor
  template <typename U, typename E>
  unique_ptr(unique_ptr<U, E>&& other) noexcept
      : ptr_(other.release()), deleter_(std::forward<E>(other.get_deleter())) {}

  unique_ptr& operator=(const unique_ptr& other) = delete;

  unique_ptr& operator=(unique_ptr&& other) noexcept {
    unique_ptr(std::move(other)).swap(*this);
    return *this;
  }

  void reset(T* ptr) noexcept {
    deleter_(ptr_);
    ptr_ = ptr;
  }

  T* release() noexcept {
    auto old_ptr = ptr_;
    ptr_ = nullptr;
    return old_ptr;
  }

  void swap(unique_ptr& other) noexcept {
    using std::swap;
    swap(ptr_, other.ptr_);
  }

  T& operator*() const noexcept { return *ptr_; }
  T* operator->() const noexcept { return ptr_; }
  T* get() const noexcept { return ptr_; }
  Deleter get_deleter() const noexcept { return deleter_; }
  explicit operator bool() { return ptr_ != nullptr; }

 private:
  T* ptr_ = nullptr;
  Deleter deleter_ = Deleter();
};

Примечание. Это простая реализация, которая не включает в себя все возможности и функции std::unique_ptr.

Основные моменты:

  1. Эта реализация unique_ptr позволяет вам передавать пользовательский модуль удаления в качестве второго параметра шаблона. Средство удаления по умолчанию — default_delete, которое вызывает delete для управляемого указателя. Однако вы можете указать пользовательское средство удаления, которое может делать все, что вы хотите, с управляемым указателем, когда unique_ptr выходит за пределы области действия.
  2. Метод перемещения и замены используется для реализации оператора перемещения-присваивания. Идея этого метода заключается в определении временного экземпляра unique_ptr и обмене его содержимым с целевым экземпляром unique_ptr. Этот подход гарантирует, что целевой экземпляр unique_ptr останется в допустимом состоянии, даже если присваивание завершится ошибкой из-за возникновения исключения. Временный экземпляр unique_ptr уничтожается в конце области действия, а счетчик ссылок динамически выделяемого объекта корректно управляется.
  3. Эта реализация обобщенного конструктора перемещения позволяет перемещать unique_ptr любого типа U и средство удаления E в unique_ptr типа T и средство удаления Deleter. Управляемый указатель передается из экземпляра other путем вызова release(), а пользовательское средство удаления передается путем вызова get_deleter() и пересылки результата конструктору Deleter. В этой реализации используется std::forward, чтобы гарантировать сохранение типа пользовательского удаления, даже если это ссылка на rvalue. Обратите внимание, что средство удаления также должно поддерживать обобщенный конструктор копирования/перемещения.

Вот несколько тестов для проверки функциональности unique_ptr:

struct Foo {
  Foo() { std::cout << "Foo created\n"; }
  ~Foo() { std::cout << "Foo destroyed\n"; }
};

struct Base {
  virtual void bar() { std::cout << "Base\n"; }
};

struct Derived : public Base {
  void bar() override { std::cout << "Derived\n"; }
};

struct FileDeleter {
  void operator()(FILE* file) const {
    std::cout << "Closing file..." << std::endl; 
    fclose(file);
  }
};

int main() {
  // Test 1: Construct unique_ptr and verify the object is created
  {
    unique_ptr<Foo> p1(new Foo); // Foo created
  } // Foo destroyed

  // Test 2: Reset unique_ptr and verify the original object is destroyed
  {
    unique_ptr<Foo> p1(new Foo); // Foo created
    p1.reset(nullptr); // Foo destroyed
  }

  // Test 3: Move unique_ptr and verify the moved-from instance has a nullptr
  {
    unique_ptr<Foo> p1(new Foo); // Foo created
    unique_ptr<Foo> p2 = std::move(p1);
    std::cout << "p1 is null: " << (p1 ? "false" : "true") << '\n'; // outputs true
  } // Foo destroyed

  // Test 4: Move assignment and verify the moved-from instance has a nullptr
  {
    unique_ptr<Foo> p1(new Foo); // Foo created
    unique_ptr<Foo> p2;
    p2 = std::move(p1);
    std::cout << "p1 is null: " << (p1 ? "false" : "true") << '\n'; // outputs true
  } // Foo destroyed

  // Test 5: Generalized copy constructor with related types
  {
    unique_ptr<Derived> p1(new Derived);
    unique_ptr<Base> p2(std::move(p1));
    p2->bar(); // outputs Derived
  }

  // Test 6: Custom deleter
  {
    unique_ptr<FILE, FileDeleter> file(tmpfile(), FileDeleter());
    if (!file) {
      std::cerr << "Failed to open temporary file." << std::endl;
      return 1;
    }
    fprintf(file.get(), "Hello, world!");
    rewind(file.get());
    char buffer[50];
    fgets(buffer, sizeof(buffer), file.get());
    std::cout << "Content of file: " << buffer << std::endl; // outputs Hello World!
  } // outputs Closing file... 

  return 0;
}

Эти тесты проверяют основные функции, такие как построение unique_ptr, сброс un

ique_ptr, перемещение unique_ptr, использование универсального конструктора перемещения для создания unique_ptr связанного типа и использование пользовательского удаления.

Связанное чтение:

  1. Практическое руководство по реализации std::shared_ptr | Джаспер Чжун | февраль 2023 г. | Гений разработчиков
  2. Углубленное руководство по std::remove_reference, std::move и std::forward | Джаспер Чжун | февраль 2023 г. | Гений разработчиков