Skip to content

Commit

Permalink
update README
Browse files Browse the repository at this point in the history
  • Loading branch information
KRM7 committed Feb 16, 2024
1 parent e767c39 commit 88191f9
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/sanitizers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
pkgs: clang-15 llvm-15

env:
ASAN_OPTIONS: check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:detect_leaks=1
ASAN_OPTIONS: check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:detect_leaks=1:detect_invalid_pointer_pairs=2
UBSAN_OPTIONS: print_stacktrace=1:print_summary=1

defaults:
Expand Down
107 changes: 100 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
A fully constexpr unique_ptr implementation in C++20 with small object optimization.

### `small_unique_ptr<T>`

A constexpr `unique_ptr` implementation in C++20 with small object optimization.

```cpp
small_unique_ptr<Base> p = make_unique_small<Derived>();
```

Objects created with `make_unique_small<T>` are allocated on the stack if:

- Their size is not greater than `64 - 2 * sizeof(void*)`
- Their size is not greater than the size of the internal stack buffer
- Their required alignment is not greater than `64`
- Their move constructor is declared as `noexcept`
- Their move constructor is `noexcept`

The size of the stack buffer is architecture dependent, but it will generally be:

- `48` for polymorphic types
- `56` for polymorphic types that implement a virtual `small_unique_ptr_move` method
- `sizeof(T)` for non-polymophic types, with an upper limit of `56`

The size of a `small_unique_ptr<T>` object is:
The overall size of a `small_unique_ptr<T>` object is:

- `64` if T can be allocated in the stack buffer
- `64` if `T` may be allocated in the stack buffer
- `sizeof(T*)` otherwise

The interface matches `std::unique_ptr<T>`, except for:
Expand All @@ -19,5 +32,85 @@ The interface matches `std::unique_ptr<T>`, except for:
- `T` can't be an incomplete type or an array type
- There are a couple of extra methods for checking where objects are allocated

The stack buffer is not used in constant evaluated contexts, so any constexpr usage
is subject to the same transient allocation requirements that `std::unique_ptr<T>` would be.
Everything is constexpr, but the stack buffer is not used in constant evaluated contexts,
so any constexpr usage is subject to the same transient allocation requirements that a constexpr
`std::unique_ptr<T>` would be.

<details>
<summary>
Example of a simplified <code>std::move_only_function</code> implementation using
<code>small_unique_ptr</code>
</summary>

```cpp
template<typename...>
class move_only_function;

template<typename Ret, typename... Args>
class move_only_function<Ret(Args...)>
{
public:
constexpr move_only_function() noexcept = default;
constexpr move_only_function(std::nullptr_t) noexcept {}

template<typename F>
requires(!std::is_same_v<F, move_only_function> && std::is_invocable_r_v<Ret, F&, Args...>)
constexpr move_only_function(F f) noexcept(noexcept(make_unique_small<Impl<F>>(std::move(f)))) :
fptr_(make_unique_small<Impl<F>>(std::move(f)))
{}

template<typename F>
requires(!std::is_same_v<F, move_only_function> && std::is_invocable_r_v<Ret, F&, Args...>)
constexpr move_only_function& operator=(F f) noexcept(noexcept(make_unique_small<Impl<F>>(std::move(f))))
{
fptr_ = make_unique_small<Impl<F>>(std::move(f));
return *this;
}

constexpr move_only_function(move_only_function&&) = default;
constexpr move_only_function& operator=(move_only_function&&) = default;

constexpr Ret operator()(Args... args) const
{
return fptr_->invoke(std::forward<Args>(args)...);
}

constexpr void swap(move_only_function& other) noexcept
{
fptr_.swap(other.fptr_);
}

constexpr explicit operator bool() const noexcept { return static_cast<bool>(fptr_); }

private:
struct ImplBase
{
constexpr virtual Ret invoke(Args...) = 0;
constexpr virtual void small_unique_ptr_move(void* dst) noexcept = 0;
constexpr virtual ~ImplBase() = default;
};

template<typename Callable>
struct Impl : public ImplBase
{
constexpr Impl(Callable func) noexcept(std::is_nothrow_move_constructible_v<Callable>) :
func_(std::move(func))
{}

constexpr void small_unique_ptr_move(void* dst) noexcept override
{
std::construct_at(static_cast<Impl*>(dst), std::move(*this));
}

constexpr Ret invoke(Args... args) override
{
return std::invoke(func_, std::forward<Args>(args)...);
}

Callable func_;
};

small_unique_ptr<ImplBase> fptr_ = nullptr;
};
```
</details>
2 changes: 1 addition & 1 deletion core-guidelines.ruleset
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for GeneticAlgorithm" Description="Code analysis rules for GeneticAlgorithm.vcxproj." ToolsVersion="17.0">
<RuleSet Name="Rules" Description="Code analysis rules." ToolsVersion="17.0">
<Include Path="cppcorecheckarithmeticrules.ruleset" Action="Default" />
<Include Path="cppcorecheckboundsrules.ruleset" Action="Default" />
<Include Path="cppcorecheckclassrules.ruleset" Action="Default" />
Expand Down
27 changes: 15 additions & 12 deletions src/small_unique_ptr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ namespace detail

pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
assert(0 <= offset && offset < buffer_size_v<T>);
return std::launder(reinterpret_cast<pointer>(std::addressof(buffer_[offset])));
return std::launder(reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset));
}

template<typename U>
Expand All @@ -127,9 +126,12 @@ namespace detail
dst.move_ = move_;
}

constexpr bool is_stack_allocated() const noexcept { return static_cast<bool>(this->move_); }
constexpr bool is_stack_allocated() const noexcept
{
return static_cast<bool>(this->move_);
}

alignas(buffer_alignment_v<T>) mutable buffer_t buffer_ = {};
alignas(buffer_alignment_v<T>) mutable buffer_t buffer_;
T* data_ = nullptr;
move_fn move_ = nullptr;
};
Expand All @@ -152,8 +154,7 @@ namespace detail

pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
assert(0 <= offset && offset < buffer_size_v<T>);
return std::launder(reinterpret_cast<pointer>(std::addressof(buffer_[offset])));
return std::launder(reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset));
}

template<typename U>
Expand All @@ -162,9 +163,12 @@ namespace detail
std::construct_at(dst.buffer(), std::move(*this->buffer()));
}

constexpr bool is_stack_allocated() const noexcept { return !std::is_constant_evaluated() && (data_ == this->buffer()); }
constexpr bool is_stack_allocated() const noexcept
{
return !std::is_constant_evaluated() && (data_ == this->buffer());
}

alignas(buffer_alignment_v<T>) mutable buffer_t buffer_ = {};
alignas(buffer_alignment_v<T>) mutable buffer_t buffer_;
T* data_ = nullptr;
};

Expand All @@ -177,8 +181,7 @@ namespace detail

pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
assert(0 <= offset && offset < buffer_size_v<T>);
return std::launder(reinterpret_cast<pointer>(std::addressof(buffer_[offset])));
return std::launder(reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset));
}

template<typename U>
Expand All @@ -202,7 +205,7 @@ namespace detail
return std::less_equal{}(buffer_first, data) && std::less{}(data, buffer_last);
}

alignas(buffer_alignment_v<T>) mutable buffer_t buffer_ = {};
alignas(buffer_alignment_v<T>) mutable buffer_t buffer_;
T* data_ = nullptr;
};

Expand Down Expand Up @@ -453,7 +456,7 @@ namespace detail
struct make_unique_small_impl
{
template<typename T, typename... Args>
static constexpr small_unique_ptr<T> invoke(Args... args)
static constexpr small_unique_ptr<T> invoke(Args&&... args)
noexcept(std::is_nothrow_constructible_v<T, Args...> && !detail::is_always_heap_allocated_v<T>)
{
small_unique_ptr<T> ptr;
Expand Down

0 comments on commit 88191f9

Please sign in to comment.