[libc-commits] [libc] [libc] Implement cpp::unique_ptr utility (PR #206701)

via libc-commits libc-commits at lists.llvm.org
Tue Jun 30 07:28:45 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Jeff Bailey (kaladron)

<details>
<summary>Changes</summary>

Implemented cpp::unique_ptr and cpp::default_delete in
libc/src/__support/CPP/unique_ptr.h for internal use within LLVM-libc.

* Added cpp::default_delete and its array specialization.
* Implemented cpp::unique_ptr with support for custom deleters and
  array specialization.
* Included is_assignable.h in type_traits.h to support conversion
  assignment constraints.
* Added comprehensive unit tests in
  libc/test/src/__support/CPP/unique_ptr_test.cpp.

This is a simplified implementation with the following limitations:
* No empty member optimization (empty deleters still occupy space).
* No support for custom pointer types (hardcoded to T*).
* No support for reference deleter types or array conversion.

Assisted-by: Automated tooling, human reviewed.

---
Full diff: https://github.com/llvm/llvm-project/pull/206701.diff


5 Files Affected:

- (modified) libc/src/__support/CPP/CMakeLists.txt (+12) 
- (modified) libc/src/__support/CPP/type_traits.h (+7-1) 
- (added) libc/src/__support/CPP/unique_ptr.h (+214) 
- (modified) libc/test/src/__support/CPP/CMakeLists.txt (+15-5) 
- (added) libc/test/src/__support/CPP/unique_ptr_test.cpp (+146) 


``````````diff
diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index b602aa1f79d08..a7f04d8b8e66f 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -201,6 +201,18 @@ add_header_library(
     expected.h
 )
 
+add_header_library(
+  unique_ptr
+  HDRS
+    unique_ptr.h
+  DEPENDS
+    .new
+    .type_traits
+    .utility
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.config
+)
+
 add_object_library(
   new
   SRCS
diff --git a/libc/src/__support/CPP/type_traits.h b/libc/src/__support/CPP/type_traits.h
index d48ee23aeae07..4bf5acdccd927 100644
--- a/libc/src/__support/CPP/type_traits.h
+++ b/libc/src/__support/CPP/type_traits.h
@@ -1,10 +1,15 @@
-//===-- Self contained C++ type_traits --------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Self contained C++ type_traits.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_H
 #define LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_H
@@ -24,6 +29,7 @@
 #include "src/__support/CPP/type_traits/invoke_result.h"
 #include "src/__support/CPP/type_traits/is_arithmetic.h"
 #include "src/__support/CPP/type_traits/is_array.h"
+#include "src/__support/CPP/type_traits/is_assignable.h"
 #include "src/__support/CPP/type_traits/is_base_of.h"
 #include "src/__support/CPP/type_traits/is_class.h"
 #include "src/__support/CPP/type_traits/is_complex.h"
diff --git a/libc/src/__support/CPP/unique_ptr.h b/libc/src/__support/CPP/unique_ptr.h
new file mode 100644
index 0000000000000..cb1fae0a5e601
--- /dev/null
+++ b/libc/src/__support/CPP/unique_ptr.h
@@ -0,0 +1,214 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Standalone implementation of std::unique_ptr.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_CPP_UNIQUE_PTR_H
+#define LLVM_LIBC_SRC___SUPPORT_CPP_UNIQUE_PTR_H
+
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/utility.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace cpp {
+
+/// A wrapper for deleting objects by default.
+template <typename T> struct default_delete {
+  LIBC_INLINE constexpr default_delete() = default;
+
+  template <typename U, typename = typename enable_if<
+                            is_convertible<U *, T *>::value>::type>
+  LIBC_INLINE constexpr default_delete(const default_delete<U> &) {}
+
+  LIBC_INLINE constexpr void operator()(T *ptr) const {
+    static_assert(sizeof(T) > 0, "cannot delete an incomplete type");
+    delete ptr;
+  }
+};
+
+/// A wrapper for deleting array objects by default.
+template <typename T> struct default_delete<T[]> {
+  LIBC_INLINE constexpr default_delete() = default;
+
+  template <typename U, typename = typename enable_if<
+                            is_convertible<U (*)[], T (*)[]>::value>::type>
+  LIBC_INLINE constexpr default_delete(const default_delete<U[]> &) {}
+
+  template <typename U, typename = typename enable_if<
+                            is_convertible<U (*)[], T (*)[]>::value>::type>
+  LIBC_INLINE constexpr void operator()(U *ptr) const {
+    static_assert(sizeof(U) > 0, "cannot delete an incomplete type");
+    delete[] ptr;
+  }
+};
+
+/// A smart pointer that owns and manages another object through a pointer.
+template <typename T, typename Deleter = default_delete<T>> class unique_ptr {
+  T *ptr_ = nullptr;
+  Deleter deleter_;
+
+  template <typename U, typename E> friend class unique_ptr;
+
+public:
+  using element_type = T;
+  using deleter_type = Deleter;
+  using pointer = T *;
+
+  LIBC_INLINE constexpr unique_ptr() = default;
+  LIBC_INLINE constexpr unique_ptr(decltype(nullptr)) : ptr_(nullptr) {}
+  LIBC_INLINE constexpr explicit unique_ptr(pointer p) : ptr_(p) {}
+
+  LIBC_INLINE constexpr unique_ptr(pointer p, const deleter_type &d)
+      : ptr_(p), deleter_(d) {}
+  LIBC_INLINE constexpr unique_ptr(pointer p, deleter_type &&d)
+      : ptr_(p), deleter_(move(d)) {}
+
+  // Move constructor
+  LIBC_INLINE constexpr unique_ptr(unique_ptr &&other)
+      : ptr_(other.release()), deleter_(forward<Deleter>(other.get_deleter())) {
+  }
+
+  // Move assignment
+  LIBC_INLINE constexpr unique_ptr &operator=(unique_ptr &&other) {
+    reset(other.release());
+    deleter_ = forward<Deleter>(other.get_deleter());
+    return *this;
+  }
+
+  // Conversion move constructor
+  template <
+      typename U, typename E,
+      typename = typename enable_if<
+          is_convertible<typename unique_ptr<U, E>::pointer, pointer>::value &&
+          !is_array<U>::value && is_convertible<E, Deleter>::value>::type>
+  LIBC_INLINE constexpr unique_ptr(unique_ptr<U, E> &&other)
+      : ptr_(other.release()), deleter_(forward<E>(other.get_deleter())) {}
+
+  // Conversion move assignment
+  template <
+      typename U, typename E,
+      typename = typename enable_if<
+          is_convertible<typename unique_ptr<U, E>::pointer, pointer>::value &&
+          !is_array<U>::value && is_assignable<Deleter &, E &&>::value>::type>
+  LIBC_INLINE constexpr unique_ptr &operator=(unique_ptr<U, E> &&other) {
+    reset(other.release());
+    deleter_ = forward<E>(other.get_deleter());
+    return *this;
+  }
+
+  // Disable copy
+  unique_ptr(const unique_ptr &) = delete;
+  unique_ptr &operator=(const unique_ptr &) = delete;
+
+  LIBC_INLINE ~unique_ptr() { reset(); }
+
+  LIBC_INLINE constexpr pointer get() const { return ptr_; }
+  LIBC_INLINE constexpr deleter_type &get_deleter() { return deleter_; }
+  LIBC_INLINE constexpr const deleter_type &get_deleter() const {
+    return deleter_;
+  }
+
+  LIBC_INLINE constexpr explicit operator bool() const {
+    return ptr_ != nullptr;
+  }
+
+  LIBC_INLINE constexpr pointer release() {
+    pointer temp = ptr_;
+    ptr_ = nullptr;
+    return temp;
+  }
+
+  LIBC_INLINE constexpr void reset(pointer p = pointer()) {
+    pointer old_ptr = ptr_;
+    ptr_ = p;
+    if (old_ptr)
+      deleter_(old_ptr);
+  }
+
+  LIBC_INLINE constexpr typename add_lvalue_reference<T>::type
+  operator*() const {
+    return *ptr_;
+  }
+  LIBC_INLINE constexpr pointer operator->() const { return ptr_; }
+};
+
+/// A smart pointer that owns and manages an array of objects through a pointer.
+template <typename T, typename Deleter> class unique_ptr<T[], Deleter> {
+  T *ptr_ = nullptr;
+  Deleter deleter_;
+
+  template <typename U, typename E> friend class unique_ptr;
+
+public:
+  using element_type = T;
+  using deleter_type = Deleter;
+  using pointer = T *;
+
+  LIBC_INLINE constexpr unique_ptr() = default;
+  LIBC_INLINE constexpr unique_ptr(decltype(nullptr)) : ptr_(nullptr) {}
+  LIBC_INLINE constexpr explicit unique_ptr(pointer p) : ptr_(p) {}
+
+  LIBC_INLINE constexpr unique_ptr(pointer p, const deleter_type &d)
+      : ptr_(p), deleter_(d) {}
+  LIBC_INLINE constexpr unique_ptr(pointer p, deleter_type &&d)
+      : ptr_(p), deleter_(move(d)) {}
+
+  // Move constructor
+  LIBC_INLINE constexpr unique_ptr(unique_ptr &&other)
+      : ptr_(other.release()), deleter_(forward<Deleter>(other.get_deleter())) {
+  }
+
+  // Move assignment
+  LIBC_INLINE constexpr unique_ptr &operator=(unique_ptr &&other) {
+    reset(other.release());
+    deleter_ = forward<Deleter>(other.get_deleter());
+    return *this;
+  }
+
+  // Disable copy
+  unique_ptr(const unique_ptr &) = delete;
+  unique_ptr &operator=(const unique_ptr &) = delete;
+
+  LIBC_INLINE ~unique_ptr() { reset(); }
+
+  LIBC_INLINE constexpr pointer get() const { return ptr_; }
+  LIBC_INLINE constexpr deleter_type &get_deleter() { return deleter_; }
+  LIBC_INLINE constexpr const deleter_type &get_deleter() const {
+    return deleter_;
+  }
+
+  LIBC_INLINE constexpr explicit operator bool() const {
+    return ptr_ != nullptr;
+  }
+
+  LIBC_INLINE constexpr pointer release() {
+    pointer temp = ptr_;
+    ptr_ = nullptr;
+    return temp;
+  }
+
+  LIBC_INLINE constexpr void reset(pointer p = pointer()) {
+    pointer old_ptr = ptr_;
+    ptr_ = p;
+    if (old_ptr)
+      deleter_(old_ptr);
+  }
+
+  LIBC_INLINE constexpr T &operator[](size_t i) const { return ptr_[i]; }
+};
+
+} // namespace cpp
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_CPP_UNIQUE_PTR_H
diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt
index 9c09684be192d..97171ddf7c996 100644
--- a/libc/test/src/__support/CPP/CMakeLists.txt
+++ b/libc/test/src/__support/CPP/CMakeLists.txt
@@ -97,7 +97,6 @@ add_libc_test(
     libc.src.__support.CPP.utility
 )
 
-
 # This test fails with invalid address space operations on sm_60
 if(NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
   add_libc_test(
@@ -132,6 +131,17 @@ add_libc_test(
     libc.src.__support.CPP.optional
 )
 
+add_libc_test(
+  unique_ptr_test
+  SUITE
+    libc-cpp-utils-tests
+  SRCS
+    unique_ptr_test.cpp
+  DEPENDS
+    libc.src.__support.CPP.unique_ptr
+    libc.src.__support.CPP.utility
+)
+
 add_libc_test(
   tuple_test
   SUITE
@@ -169,8 +179,8 @@ add_libc_test(
   SRCS
     string_test.cpp
   DEPENDS
-  libc.src.__support.CPP.string
-  libc.src.__support.CPP.string_view
+    libc.src.__support.CPP.string
+    libc.src.__support.CPP.string_view
 )
 
 add_libc_test(
@@ -178,9 +188,9 @@ add_libc_test(
   SUITE
     libc-cpp-utils-tests
   SRCS
-  type_traits_test.cpp
+    type_traits_test.cpp
   DEPENDS
-  libc.src.__support.CPP.type_traits
+    libc.src.__support.CPP.type_traits
 )
 
 if(LIBC_COMPILER_HAS_EXT_VECTOR_TYPE)
diff --git a/libc/test/src/__support/CPP/unique_ptr_test.cpp b/libc/test/src/__support/CPP/unique_ptr_test.cpp
new file mode 100644
index 0000000000000..768aa22e07ced
--- /dev/null
+++ b/libc/test/src/__support/CPP/unique_ptr_test.cpp
@@ -0,0 +1,146 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for UniquePtr.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/unique_ptr.h"
+#include "src/__support/CPP/utility.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::cpp::unique_ptr;
+
+struct DestructTracker {
+  int *destruct_count = nullptr;
+  DestructTracker() = default;
+  DestructTracker(int *count) : destruct_count(count) {}
+  ~DestructTracker() {
+    if (destruct_count)
+      (*destruct_count)++;
+  }
+};
+
+// Test basic construction, ownership, and destruction.
+TEST(LlvmLibcUniquePtrTest, Basic) {
+  int destruct_count = 0;
+  {
+    unique_ptr<DestructTracker> ptr(new DestructTracker(&destruct_count));
+    ASSERT_TRUE(static_cast<bool>(ptr));
+    ASSERT_EQ(destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 1);
+}
+
+// Test nullptr construction and behavior.
+TEST(LlvmLibcUniquePtrTest, Nullptr) {
+  unique_ptr<int> ptr(nullptr);
+  ASSERT_FALSE(static_cast<bool>(ptr));
+  ASSERT_EQ(ptr.get(), static_cast<int *>(nullptr));
+}
+
+// Test move construction transferring ownership.
+TEST(LlvmLibcUniquePtrTest, Move) {
+  int destruct_count = 0;
+  {
+    unique_ptr<DestructTracker> ptr1(new DestructTracker(&destruct_count));
+    unique_ptr<DestructTracker> ptr2(LIBC_NAMESPACE::cpp::move(ptr1));
+    ASSERT_FALSE(static_cast<bool>(ptr1));
+    ASSERT_TRUE(static_cast<bool>(ptr2));
+    ASSERT_EQ(destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 1);
+}
+
+// Test move assignment transferring ownership and releasing old resource.
+TEST(LlvmLibcUniquePtrTest, MoveAssignment) {
+  int destruct_count1 = 0;
+  int destruct_count2 = 0;
+  {
+    unique_ptr<DestructTracker> ptr1(new DestructTracker(&destruct_count1));
+    unique_ptr<DestructTracker> ptr2(new DestructTracker(&destruct_count2));
+    ptr2 = LIBC_NAMESPACE::cpp::move(ptr1);
+    ASSERT_FALSE(static_cast<bool>(ptr1));
+    ASSERT_TRUE(static_cast<bool>(ptr2));
+    ASSERT_EQ(destruct_count1, 0);
+    ASSERT_EQ(destruct_count2, 1); // ptr2's original object should be destroyed
+  }
+  ASSERT_EQ(destruct_count1, 1);
+}
+
+// Test release of ownership without destroying the object.
+TEST(LlvmLibcUniquePtrTest, Release) {
+  int destruct_count = 0;
+  DestructTracker *raw_ptr = nullptr;
+  {
+    unique_ptr<DestructTracker> ptr(new DestructTracker(&destruct_count));
+    raw_ptr = ptr.release();
+    ASSERT_FALSE(static_cast<bool>(ptr));
+    ASSERT_EQ(destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 0);
+  delete raw_ptr;
+  ASSERT_EQ(destruct_count, 1);
+}
+
+// Test reset replacing the owned object and destroying the old one.
+TEST(LlvmLibcUniquePtrTest, Reset) {
+  int destruct_count1 = 0;
+  int destruct_count2 = 0;
+  {
+    unique_ptr<DestructTracker> ptr(new DestructTracker(&destruct_count1));
+    ptr.reset(new DestructTracker(&destruct_count2));
+    ASSERT_EQ(destruct_count1, 1);
+    ASSERT_EQ(destruct_count2, 0);
+  }
+  ASSERT_EQ(destruct_count2, 1);
+}
+
+// Test dereference operators (operator* and operator->).
+TEST(LlvmLibcUniquePtrTest, Dereference) {
+  struct Foo {
+    int val;
+  };
+  unique_ptr<Foo> ptr(new Foo{42});
+  ASSERT_EQ((*ptr).val, 42);
+  ASSERT_EQ(ptr->val, 42);
+}
+
+// Test array specialization behavior and destruction.
+TEST(LlvmLibcUniquePtrTest, Array) {
+  int destruct_count = 0;
+  {
+    unique_ptr<DestructTracker[]> ptr(new DestructTracker[3]);
+    ptr[0].destruct_count = &destruct_count;
+    ptr[1].destruct_count = &destruct_count;
+    ptr[2].destruct_count = &destruct_count;
+    ASSERT_TRUE(static_cast<bool>(ptr));
+    ASSERT_EQ(destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 3);
+}
+
+struct CustomDeleter {
+  int *count;
+  void operator()(int *p) const {
+    (*count)++;
+    delete p;
+  }
+};
+
+// Test support for custom deleters.
+TEST(LlvmLibcUniquePtrTest, CustomDeleter) {
+  int deleter_count = 0;
+  {
+    unique_ptr<int, CustomDeleter> ptr(new int(42),
+                                       CustomDeleter{&deleter_count});
+    ASSERT_EQ(deleter_count, 0);
+  }
+  ASSERT_EQ(deleter_count, 1);
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/206701


More information about the libc-commits mailing list