Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Please read the [Code of Conduct](CODE_OF_CONDUCT.md).
* Radiant uses Bazel as the primary build system. Install Bazel
([official instructions][bazel.install]). [Bazelisk][bazel.bazelisk] is
recommended.
* Python is required for tooling. Install Python
* Python (3.11+) is required for tooling. Install Python
([official instructions][python.install]).
* Radiant uses python package to aid in the remaining setup and subsequent
workflows. Install it into your python environment by running:
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ additional information or guidance.

## Disclosure Policy

When the Raditn team receives a security bug report, they will assign it to a
When the Radiant team receives a security bug report, they will assign it to a
primary developer. This person will coordinate the fix and release process,
involving the following steps:

Expand Down
169 changes: 169 additions & 0 deletions radiant/UniquePtr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// radiant/UniquePtr.h
// Copyright 2025 The Radiant Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "radiant/Utility.h" // rad::Move
#include "radiant/Algorithm.h" // rad::Swap
#include "radiant/detail/StdTypeTraits.h" // rad::IsConv
#include <cstddef> // nullptr_t

namespace rad
{

/// @brief Empty deleter type so it occupies no storage (EBO).
template <typename T>
struct DefaultDelete {
constexpr DefaultDelete() noexcept = default;

/// @brief Allow DefaultDelete<Base> to be constructed from DefaultDelete<Derived>
template <typename U,
rad::EnIf<rad::IsConv<U*, T*>, int> = 0>
constexpr DefaultDelete(const DefaultDelete<U>&) noexcept {}

void operator()(T* p) const noexcept { delete p; }
};

/// @brief UniquePtr<T, Deleter> owns a T* and invokes Deleter when destroyed.
/// @details If Deleter is empty, sizeof(UniquePtr) == sizeof(T*).
template <typename T, typename Deleter = DefaultDelete<T>>
class UniquePtr : private Deleter
{
public:
RAD_NOT_COPYABLE(UniquePtr);

using pointer = T*;
using deleter_type = Deleter;

/// @brief Default constructs to nullptr.
constexpr UniquePtr() noexcept
: Deleter(), // base subobject
m_ptr(nullptr)
{}

/// @brief Constructs owning a raw pointer + optional custom deleter.
explicit constexpr UniquePtr(pointer p, Deleter d = Deleter()) noexcept
: Deleter(rad::Move(d)),
m_ptr(p)
{}

/// @brief Constructs owning a raw pointer + optimal custom deleter.
template <class U, class E,
rad::EnIf<rad::IsConv<U*, T*>, int> = 0>
constexpr UniquePtr(UniquePtr<U, E>&& o) noexcept
: Deleter(rad::Move(o.get_deleter())),
m_ptr(o.release())
{}

/// @brief Move-constructs, stealing ownership
constexpr UniquePtr(UniquePtr&& o) noexcept
: Deleter(rad::Move(o.get_deleter())),
m_ptr(o.release())
{
o.m_ptr = nullptr;
}

/// @brief Destroys the managed object if non-null.
~UniquePtr() noexcept
{
if (m_ptr) static_cast<Deleter&>(*this)(m_ptr);
}

/// @brief nullptr-assignment (clears the pointer)
UniquePtr& operator=(std::nullptr_t) noexcept
{
reset();
return *this;
}

/// @brief Assigns a raw pointer, deleting the current one.
template <class U, class E,
rad::EnIf<rad::IsConv<U*, T*>, int> = 0>
UniquePtr& operator=(UniquePtr<U, E>&& o) noexcept
{
reset(o.release());
get_deleter() = rad::Move(o.get_deleter());
return *this;
}

/// @brief Move-assigns, destroying current and stealing ownership.
UniquePtr& operator=(UniquePtr&& o) noexcept
{
if (this != &o)
{
reset();
m_ptr = o.m_ptr;
get_deleter() = rad::Move(o.get_deleter());
o.m_ptr = nullptr;
}
return *this;
}

// --- OBSERVERS ---

/// @return the stored pointer (may be nullptr)
constexpr pointer get() const noexcept { return m_ptr; }

/// @return reference to *get()
constexpr T& operator*() const noexcept { return *m_ptr; }

/// @return get(), for pointer‐like syntax
constexpr pointer operator->() const noexcept { return m_ptr; }

/// @return true if get() != nullptr
explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; }

// --- MODIFIERS ---

/// @brief Release ownership without deleting; returns previous pointer.
pointer release() noexcept
{
pointer tmp = m_ptr;
m_ptr = nullptr;
return tmp;
}

/// @brief Delete current and take new pointer p (default nullptr)
void reset(pointer p = nullptr) noexcept
{
if (m_ptr) static_cast<Deleter&>(*this)(m_ptr);
m_ptr = p;
}

/// @brief Swap pointers and deleters with another UniquePtr
void swap(UniquePtr& o) noexcept
{
using rad::Swap;
Swap(m_ptr, o.m_ptr);
Swap(get_deleter(), o.get_deleter());
}

/// @return reference to the stored deleter
constexpr Deleter& get_deleter() noexcept { return static_cast<Deleter&>(*this); }
constexpr const Deleter& get_deleter() const noexcept { return static_cast<const Deleter&>(*this); }

private:
pointer m_ptr;
};

/// @brief ADL‐enabled swap overload
template <typename T, typename D>
void swap(UniquePtr<T, D>& a, UniquePtr<T, D>& b) noexcept
{
a.swap(b);
}

/// @brief Alias using default deleter
template <typename T>
using UniquePtrDefault = UniquePtr<T>;

} // namespace rad
158 changes: 158 additions & 0 deletions test/test_UniquePtr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// test/test_UniquePtr.cpp
// Copyright 2025 The Radiant Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "gtest/gtest.h"
#include "radiant/UniquePtr.h"

// simple type to track live instances
struct Foo {
static int live;
int value;
Foo(int v) : value(v) { ++live; }
~Foo() { --live; }
};
int Foo::live = 0;

// custom deleter functor for testing
struct D {
static int count;
void operator()(int* p) const noexcept {
++count;
delete p;
}
};
int D::count = 0;

TEST(UniquePtr, BasicLifetimeAndSize) {
// the default deleter is empty, so this must be one pointer in size
static_assert(sizeof(rad::UniquePtrDefault<Foo>) == sizeof(Foo*),
"UniquePtrDefault<Foo> must be one pointer");

EXPECT_EQ(Foo::live, 0);
{
rad::UniquePtrDefault<Foo> p(new Foo{42});
EXPECT_TRUE(p);
EXPECT_EQ(p->value, 42);
EXPECT_EQ(Foo::live, 1);
}
// destructor should have run
EXPECT_EQ(Foo::live, 0);
}

TEST (UniquePtr, NullptrAssignment) {
rad::UniquePtrDefault<int> p(new int{3});
EXPECT_TRUE(p);
p = nullptr;
EXPECT_FALSE(p);
}

TEST(UniquePtr, ReleaseAndReset) {
Foo::live = 0;
Foo* raw = new Foo{7};
rad::UniquePtrDefault<Foo> p(raw);
EXPECT_TRUE(p);
EXPECT_EQ(Foo::live, 1);

Foo* r = p.release();
EXPECT_EQ(r, raw);
EXPECT_FALSE(p);
// user must delete now:
delete r;
EXPECT_EQ(Foo::live, 0);

// reset to new object
p.reset(new Foo{5});
EXPECT_TRUE(p);
EXPECT_EQ(p->value, 5);
EXPECT_EQ(Foo::live, 1);

// reset to nullptr
p.reset();
EXPECT_FALSE(p);
EXPECT_EQ(Foo::live, 0);
}

TEST(UniquePtr, MoveSemantics) {
Foo::live = 0;
rad::UniquePtrDefault<Foo> a(new Foo{1});
EXPECT_TRUE(a);

rad::UniquePtrDefault<Foo> b(std::move(a));
EXPECT_FALSE(a);
EXPECT_TRUE(b);
EXPECT_EQ(b->value, 1);

// move-assign
Foo::live = 1;
rad::UniquePtrDefault<Foo> c;
c = std::move(b);
EXPECT_FALSE(b);
EXPECT_TRUE(c);
EXPECT_EQ(c->value, 1);

// cleanup
c.reset();
EXPECT_EQ(Foo::live, 0);
}

struct Base { virtual ~Base() = default; };
struct Derived : Base { int v = 9; };

TEST(UniquePtr, ConvertingMove) {
rad::UniquePtr<Derived> d(new Derived{});
rad::UniquePtr<Base> b(std::move(d));
EXPECT_TRUE(b);
EXPECT_FALSE(d);

rad::UniquePtr<Derived> d2(new Derived{});
b = std::move(d2);
EXPECT_TRUE(b);
EXPECT_FALSE(d2);
}

TEST(UniquePtr, Swap) {
Foo::live = 0;
rad::UniquePtrDefault<Foo> x(new Foo{10});
rad::UniquePtrDefault<Foo> y(new Foo{20});
EXPECT_EQ(x->value, 10);
EXPECT_EQ(y->value, 20);

x.swap(y);
EXPECT_EQ(x->value, 20);
EXPECT_EQ(y->value, 10);

// ADL swap
using std::swap;
swap(x, y);
EXPECT_EQ(x->value, 10);
EXPECT_EQ(y->value, 20);

// cleanup
x.reset();
y.reset();
EXPECT_EQ(Foo::live, 0);
}

TEST(UniquePtr, CustomDeleter) {
D::count = 0;
int* raw = new int(99);

// UniquePtr<int, D> uses our functor D
rad::UniquePtr<int, D> p(raw, D());
EXPECT_TRUE(p);
EXPECT_EQ(*p, 99);
EXPECT_EQ(D::count, 0);

p.reset();
EXPECT_FALSE(p);
EXPECT_EQ(D::count, 1);
}
Loading