[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