首页 > 其他分享 >实现一个简单的 std::unique_ptr

实现一个简单的 std::unique_ptr

时间:2024-03-10 17:14:13浏览次数:26  
标签:std noexcept UniquePtr unique ptr deleter

实现一个简单的 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[]> 的重载版本,大体是类似的,这里就不实现代码了。

标签:std,noexcept,UniquePtr,unique,ptr,deleter
From: https://www.cnblogs.com/FlandreScarlet/p/18064385

相关文章

  • 关于SAP-APP机器-R3trans -d报错-R3trans: /lib64/libstdc++.so.6: version `GLIBCXX_
    在SAP-应用-APP-机器上执行如下命令报错awpxxx03:prdadm270>R3trans-dR3trans:/lib64/libstdc++.so.6:version`GLIBCXX_3.4.26'notfound(requiredbyR3trans) 其实之前,使用过一种方法解决这个问题,可以参考笔者另一篇文章《关于Redhat-Linux中-compat-sap-c++的说......
  • ULID(Universally Unique Lexicographically Sortable Identifier)是一种用于生成全局唯
    ULID(UniversallyUniqueLexicographicallySortableIdentifier)是一种用于生成全局唯一、可按字典序排序的标识符的格式。ULID结合了时间戳和随机数的特性,旨在提供高性能、低碰撞、可排序和易读的标识符。ULID的主要特点包括:全局唯一性:通过结合时间戳和随机数的方式,ULID可以生......
  • 【C++ STL容器set 】std::set 的全方位解析
    装载自知乎(虽然有AI辅助操作,但是确实写得好好):【C++STL容器set】std::set的全方位解析-知乎(zhihu.com)<imgsrc="https://pic3.zhimg.com/v2-cc8068b8931c7f65e9a89717e2ab404e_b.jpg"data-size="normal"data-rawwidth="1024"data-rawheight="1024......
  • Linux shell 标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr)介绍
    Linux系统shell使用文件描述符0与进程的标准输入(一般是键盘)相关联,文件描述符1与标准输出(一般是显示器)相关联,文件描述符2与标准出错输出(一般是显示器)相关联。1、可以将/dev/null看作"黑洞".它非常等价于一个只写文件.所有写入它的内容都会永远丢失.而尝试从它那儿读取内容则什......
  • Go语言精进之路读书笔记第43条——使用testdata管理测试依赖的外部数据文件
    43.1testdata目录Go语言规定:Go工具链将忽略名为testdata的目录。开发者可以在名为testdata的目录下存放和管理测试代码依赖的数据文件,数据文件可作为输入也可作为输出gotest命令在执行时会将被测试程序包源码所在目录设置为其工作目录,可以这样使用f,err:=os.Open("testda......
  • 随笔记录篇——C++iostream库 以及std
    这篇文章非原创,来自我学习过程中看到的其他博主发的一些资料,解决了我的疑问,在此进行整理。C语言的标准输入输出库是stdio.h库,是一个函数库,而不是类库。其中包含了我们其中所用的scanfpringf都是一些独立的全局函数,因为C语言是不支持类的。C++的标准输入输出库iostream是一个类......
  • shared_ptr, unique_ptr,weak_ptr
    #include<iostream>#include<memory>classMyClass{public:MyClass(){std::cout<<"MyClassconstructed"<<std::endl;}~MyClass(){std::cout<<"MyClassdestructed"<<std::endl;}v......
  • std::chrono类
    std::chrono是C++标准库中的一个模块,用于测量时间点和持续时间。它提供了多种时钟(clock)类型、时间点(time_point)类型、持续时间(duration)类型以及相关的函数和操作符,使得开发者能够在不同的精度和语义需求下,以一致的方式处理和操作时间。主要组件时钟(Clocks):std::chrono::sy......
  • 开源远程桌面rustdesk部署
    官网https://rustdesk.com/客户端支持的平台:x86-64: Windows | Ubuntu | MacAArch64(ARM64): Ubuntu | Android | Mac github地址:https://github.com/rustdesk/rustdesk 服务端:https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/docker/TCP(2......
  • UUID(Universally Unique Identifier,通用唯一识别码)起源 发展阶段 作用 功能 价值 组
    UUID(UniversallyUniqueIdentifier,通用唯一识别码)UUID(通用唯一识别码)的起源可以追溯到计算机科学领域。它最初由开放软件基金会(OpenSoftwareFoundation,OSF)制定,并在分布式计算环境中广泛使用。UUID的目的是确保在分布式系统中生成唯一的标识符,以避免不同节点或应用程序生成相......