Skip to content

Commit f9dd568

Browse files
authored
Implement ctl::unique_ptr (#1216)
The way unique_ptr is supposed to work is as a purely compile-time check that your raw pointers are getting deleted when they go out of scope. It should ideally emit the same exact machine code as if you were using raw pointers with manual deletes. Part of what this means is that under normal circumstances, a unique_ptr shouldn’t take up more space than a raw pointer - in other words, sizeof unique_ptr<T> should == sizeof(T*). The present PR doesn’t bother with the specialization for array types. I also left a couple other parts of the STL API unimplemented. I’d love to see someone else implement these, or I’ll get to them at some point.
1 parent e38a6e7 commit f9dd568

File tree

3 files changed

+392
-0
lines changed

3 files changed

+392
-0
lines changed

ctl/unique_ptr.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*-
2+
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
3+
//
4+
// Copyright 2024 Justine Alexandra Roberts Tunney
5+
//
6+
// Permission to use, copy, modify, and/or distribute this software for
7+
// any purpose with or without fee is hereby granted, provided that the
8+
// above copyright notice and this permission notice appear in all copies.
9+
//
10+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11+
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12+
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13+
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14+
// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15+
// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16+
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17+
// PERFORMANCE OF THIS SOFTWARE.
18+
19+
#include "unique_ptr.h"

ctl/unique_ptr.h

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
2+
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
3+
#ifndef COSMOPOLITAN_CTL_UNIQUE_PTR_H_
4+
#define COSMOPOLITAN_CTL_UNIQUE_PTR_H_
5+
#include <__utility/forward.h>
6+
#include <__utility/move.h>
7+
#include <__utility/swap.h>
8+
9+
namespace ctl {
10+
11+
template<typename T>
12+
struct default_delete
13+
{
14+
constexpr void operator()(T* p) const noexcept
15+
{
16+
delete p;
17+
}
18+
};
19+
20+
template<typename T, typename D = default_delete<T>>
21+
struct unique_ptr
22+
{
23+
using pointer = T*;
24+
using element_type = T;
25+
using deleter_type = D;
26+
27+
pointer p;
28+
[[no_unique_address]] deleter_type d;
29+
30+
constexpr unique_ptr(nullptr_t = nullptr) noexcept : p(nullptr)
31+
{
32+
}
33+
34+
constexpr unique_ptr(pointer p) noexcept : p(p)
35+
{
36+
}
37+
38+
constexpr unique_ptr(pointer p, auto&& d) noexcept
39+
: p(p), d(std::forward<decltype(d)>(d))
40+
{
41+
}
42+
43+
constexpr unique_ptr(unique_ptr&& u) noexcept : p(u.p), d(std::move(u.d))
44+
{
45+
u.p = nullptr;
46+
}
47+
48+
// TODO(mrdomino):
49+
// template <typename U, typename E>
50+
// unique_ptr(unique_ptr<U, E>&& u) noexcept;
51+
52+
unique_ptr(const unique_ptr&) = delete;
53+
54+
inline ~unique_ptr() /* noexcept */
55+
{
56+
reset();
57+
}
58+
59+
inline unique_ptr& operator=(unique_ptr r) noexcept
60+
{
61+
swap(r);
62+
return *this;
63+
}
64+
65+
inline pointer release() noexcept
66+
{
67+
pointer r = p;
68+
p = nullptr;
69+
return r;
70+
}
71+
72+
inline void reset(nullptr_t = nullptr) noexcept
73+
{
74+
if (p)
75+
d(p);
76+
p = nullptr;
77+
}
78+
79+
template<typename U>
80+
// TODO(mrdomino):
81+
/* requires is_convertible_v<U, T> */
82+
inline void reset(U* p2)
83+
{
84+
if (p) {
85+
d(p);
86+
}
87+
p = static_cast<pointer>(p2);
88+
}
89+
90+
inline void swap(unique_ptr& r) noexcept
91+
{
92+
using std::swap;
93+
swap(p, r.p);
94+
swap(d, r.d);
95+
}
96+
97+
inline pointer get() const noexcept
98+
{
99+
return p;
100+
}
101+
102+
inline deleter_type& get_deleter() noexcept
103+
{
104+
return d;
105+
}
106+
107+
inline const deleter_type& get_deleter() const noexcept
108+
{
109+
return d;
110+
}
111+
112+
inline explicit operator bool() const noexcept
113+
{
114+
return p;
115+
}
116+
117+
inline element_type& operator*() const
118+
noexcept(noexcept(*std::declval<pointer>()))
119+
{
120+
if (!p)
121+
__builtin_trap();
122+
return *p;
123+
}
124+
125+
inline pointer operator->() const noexcept
126+
{
127+
if (!p)
128+
__builtin_trap();
129+
return p;
130+
}
131+
};
132+
133+
template<typename T, typename... Args>
134+
inline unique_ptr<T>
135+
make_unique(Args&&... args)
136+
{
137+
return unique_ptr<T>(new T(std::forward<Args>(args)...));
138+
}
139+
140+
template<typename T>
141+
inline unique_ptr<T>
142+
make_unique_for_overwrite()
143+
{
144+
#if 0
145+
// You'd think that it'd work like this, but std::unique_ptr does not.
146+
return unique_ptr<T>(
147+
static_cast<T*>(::operator new(sizeof(T), align_val_t(alignof(T)))));
148+
#else
149+
return unique_ptr<T>(new T);
150+
#endif
151+
}
152+
153+
// TODO(mrdomino): specializations for T[]
154+
155+
} // namespace ctl
156+
#endif // COSMOPOLITAN_CTL_UNIQUE_PTR_H_

test/ctl/unique_ptr_test.cc

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*-
2+
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
3+
//
4+
// Copyright 2024 Justine Alexandra Roberts Tunney
5+
//
6+
// Permission to use, copy, modify, and/or distribute this software for
7+
// any purpose with or without fee is hereby granted, provided that the
8+
// above copyright notice and this permission notice appear in all copies.
9+
//
10+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11+
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12+
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13+
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14+
// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15+
// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16+
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17+
// PERFORMANCE OF THIS SOFTWARE.
18+
19+
#include "ctl/unique_ptr.h"
20+
21+
#include <type_traits>
22+
23+
#include "libc/runtime/runtime.h"
24+
25+
// #include <memory>
26+
// #define ctl std
27+
28+
template<typename T, typename D = ctl::default_delete<T>>
29+
using Ptr = ctl::unique_ptr<T, D>;
30+
31+
template<typename T, typename... Args>
32+
Ptr<T>
33+
Mk(Args&&... args)
34+
{
35+
return ctl::make_unique<T, Args...>(std::forward<Args>(args)...);
36+
}
37+
38+
template<typename T>
39+
Ptr<T>
40+
MkRaw()
41+
{
42+
return ctl::make_unique_for_overwrite<T>();
43+
}
44+
45+
#undef ctl
46+
47+
static int g = 0;
48+
49+
struct SetsGDeleter
50+
{
51+
void operator()(auto*) const noexcept
52+
{
53+
++g;
54+
}
55+
};
56+
57+
struct StatefulDeleter
58+
{
59+
char state;
60+
void operator()(auto*) const noexcept
61+
{
62+
}
63+
};
64+
65+
struct FinalDeleter final
66+
{
67+
void operator()(auto*) const noexcept
68+
{
69+
}
70+
};
71+
72+
static_assert(sizeof(Ptr<int, SetsGDeleter>) == sizeof(int*));
73+
74+
// not everyone uses [[no_unique_address]]...
75+
static_assert(!std::is_same_v<Ptr<int>, ctl::unique_ptr<int>> ||
76+
sizeof(Ptr<int, FinalDeleter>) == sizeof(int*));
77+
78+
struct SetsGCtor
79+
{
80+
SetsGCtor()
81+
{
82+
++g;
83+
}
84+
};
85+
86+
struct SetsGDtor
87+
{
88+
~SetsGDtor()
89+
{
90+
++g;
91+
}
92+
};
93+
94+
int
95+
main()
96+
{
97+
{
98+
Ptr<int> x(new int(5));
99+
}
100+
101+
{
102+
Ptr<int, SetsGDeleter> x(new int());
103+
x.reset();
104+
if (g != 1)
105+
return 1;
106+
}
107+
108+
{
109+
g = 0;
110+
Ptr<int, SetsGDeleter> x(new int());
111+
delete x.release();
112+
x.reset();
113+
if (g)
114+
return 17;
115+
}
116+
117+
{
118+
Ptr<int> x(new int(5)), y(new int(6));
119+
x.swap(y);
120+
if (*x != 6 || *y != 5)
121+
return 2;
122+
}
123+
124+
{
125+
Ptr<int> x;
126+
if (x)
127+
return 3;
128+
x.reset(new int(5));
129+
if (!x)
130+
return 4;
131+
}
132+
133+
{
134+
g = 0;
135+
Ptr<SetsGCtor> x;
136+
if (g)
137+
return 5;
138+
x = Mk<SetsGCtor>();
139+
if (g != 1)
140+
return 6;
141+
}
142+
143+
{
144+
g = 0;
145+
auto x = Mk<SetsGDtor>();
146+
if (g)
147+
return 7;
148+
x.reset();
149+
if (g != 1)
150+
return 8;
151+
if (x)
152+
return 9;
153+
}
154+
155+
{
156+
g = 0;
157+
Ptr<SetsGDtor> x, y;
158+
x = Mk<SetsGDtor>();
159+
y = Mk<SetsGDtor>();
160+
#if 0
161+
// shouldn't compile
162+
x = y;
163+
#endif
164+
x = std::move(y);
165+
if (g != 1)
166+
return 10;
167+
if (y)
168+
return 11;
169+
}
170+
171+
{
172+
g = 0;
173+
{
174+
auto x = Mk<SetsGDtor>();
175+
}
176+
if (g != 1)
177+
return 12;
178+
}
179+
180+
{
181+
g = 0;
182+
{
183+
auto x = Mk<SetsGDtor>();
184+
x.release();
185+
}
186+
if (g)
187+
return 13;
188+
}
189+
190+
#if 0
191+
// I could not figure out how to test make_unique_for_overwrite. The only
192+
// side effects it has are illegal to detect?
193+
{
194+
g = 0;
195+
auto x = MkRaw<DefaultInitialized>();
196+
if (g)
197+
return 14;
198+
x.reset();
199+
if (g)
200+
return 15;
201+
x = Mk<DefaultInitialized>();
202+
if (g != 1)
203+
return 16;
204+
}
205+
#endif
206+
207+
{
208+
int a;
209+
// Should compile.
210+
Ptr<int, FinalDeleter> x(&a);
211+
Ptr<int, StatefulDeleter> y(&a);
212+
}
213+
214+
// next is 18
215+
216+
return 0;
217+
}

0 commit comments

Comments
 (0)