Goal: No raw pointers
A raw pointer is any type with owning pointer semantics including:
T*
shared_ptr<T>
unique_ptr<T>
weak_ptr<T>
It does not include iterators, even if the iterator is a pointer type, or a pointer used to represent an optional reference
Polymorphism is the provision of a single interface to entities of different types.
In C++, runtime polymorphism is typically achieved with subtyping using inheritance.
namespace v3 {
using circle = int;
void draw(const circle& a, ostream& out, size_t position) {
out << string(position, ' ') << "circle: " << a << "\n";
}
using document = vector<circle>;
void draw(const document& a, ostream& out, size_t position) {
out << string(position, ' ') << "{\n";
for (const auto& e : a)
draw(e, out, position + 2);
out << string(position, ' ') << "}\n";
}
} // namespace v3
{
using namespace v3;
document d;
d.emplace_back(circle{5});
d.emplace_back(circle{42});
draw(d, cout, 0);
}
namespace v4 {
class shape {
public:
virtual ~shape() = default;
virtual void draw(ostream&, size_t) const = 0;
};
using document = vector<shared_ptr<shape>>;
void draw(const document& a, ostream& out, size_t position) {
out << string(position, ' ') << "{\n";
for (const auto& e : a)
e->draw(out, position + 2);
out << string(position, ' ') << "}\n";
}
} // namespace v4
namespace v4 {
class circle final : public shape {
int _radius;
public:
explicit circle(int radius) : _radius{radius} {}
void draw(ostream& out, size_t position) const override {
out << string(position, ' ') << "circle: " << _radius << "\n";
}
};
} // namespace v4
namespace v4 {
class rectangle final : public shape {
int _width, _height;
public:
explicit rectangle(int width, int height) : _width{width}, _height{height} {}
void draw(ostream& out, size_t position) const override {
out << string(position, ' ') << "rectangle: " << _width << ", " << _height << "\n";
}
};
} // namespace 4
{
using namespace v4;
document d;
d.emplace_back(new circle{5});
d.emplace_back(new rectangle{10, 42});
draw(d, cout, 0);
}
d.emplace_back(new circle{5});
circle
will be allocated firstcircle
is leaked{
using namespace v4;
document d;
d.push_back(make_shared<circle>(5));
d.push_back(make_shared<rectangle>(10, 42));
draw(d, cout, 0);
}
Pointers create incidental data structures
Question: What is the size of a shared_ptr<>
?
sizeof(shared_ptr<int>) / sizeof(void*)
A runtime concept-based polymorphic type is a type that can hold any object type which models a concept
std::any
and std::function<>
are examples of concept-based polymorphic typesnamespace v5 {
class shape {
struct concept_t;
template <class T> struct model_t;
std::shared_ptr<const concept_t> _model;
public:
template <class T>
shape(T&& s); // <-- (1)
shape() = default;
shape(const shape&) noexcept = default;
shape(shape&&) noexcept = default;
shape& operator=(const shape&) noexcept = default;
shape& operator=(shape&&) noexcept = default;
friend void draw(const shape& s, ostream& out, size_t position); // <-- (2)
};
} // namespace v5
namespace v5 {
using document = std::vector<shape>;
void draw(const document& a, ostream& out, size_t position) {
out << string(position, ' ') << "{\n";
for (const auto& e : a)
draw(e, out, position + 2);
out << string(position, ' ') << "}\n";
}
} // namespace v5
namespace v5 {
struct shape::concept_t {
virtual ~concept_t() = default;
virtual void draw_(ostream& out, size_t position) const = 0; // <-- (2)
};
} // namespace v5
namespace v5 {
template <class T>
void draw(const T& x, ostream& out, size_t position) { // <-- (2)
out << string(position, ' ') << type_name<T>() << ": " << x << "\n";
}
}
namespace v5 {
template <class T>
struct shape::model_t final : concept_t {
T _value;
template <class U = T>
model_t(U&& x) : _value{std::forward<U>(x)} {} // <-- (1)
void draw_(ostream& out, size_t position) const override { // <-- (2)
draw(_value, out, position);
}
};
} // namespace v5
namespace v5 {
template <class T>
shape::shape(T&& s)
: _model{std::make_shared<model_t<std::decay_t<T>>>(std::forward<T>(s))} {} // <-- (1)
void draw(const shape& s, ostream& out, size_t position) { // <-- (2)
s._model->draw_(out, position);
}
} // namespace v5
namespace v5 {
class circle {
int _radius;
public:
explicit circle(int radius) : _radius{radius} {}
friend void draw(const circle& s, ostream& out, size_t position) {
out << string(position, ' ') << "circle: " << s._radius << "\n";
}
};
} // namespace v5
namespace v5 {
class rectangle {
int _width, _height;
public:
explicit rectangle(int width, int height) : _width{width}, _height{height} {}
friend void draw(const rectangle& s, ostream& out, size_t position) {
out << string(position, ' ') << "rectangle: " << s._width << ", " << s._height << "\n";
}
};
} // namespace v5
{
using namespace v5;
document d;
d.emplace_back(circle{5});
d.emplace_back(rectangle{10, 42});
draw(d, cout, 0);
}
{
using namespace v5;
document d;
d.emplace_back(circle{5});
d.emplace_back(rectangle{10, 42});
d.emplace_back(string{"Hello World!"});
draw(d, cout, 0);
}
{
using namespace v5;
document d;
d.emplace_back(circle{5});
d.emplace_back(rectangle{10, 42});
d.emplace_back(d);
d.emplace_back(string{"Hello World!"});
draw(d, cout, 0);
}
namespace v5 {
using document = std::vector<shape>;
void draw(const document& a, ostream& out, size_t position) {
out << string(position, ' ') << "{\n";
for (const auto& e : a)
draw(e, out, position + 2);
out << string(position, ' ') << "}\n";
}
} // namespace v5
shared_ptr<>
is an implementation detailExercise: Read the documentation for std::function<>
, write a similar class that supports move-only invocable objects.
?std::function
namespace v1 {
template <class> class task;
template <class R, class... Args>
class task<R(Args...)> {
struct concept_t;
template <class F> struct model_t;
unique_ptr<concept_t> _model;
public:
template <class F>
task(F&&); // <-- (1)
constexpr task() noexcept = default;
task(task&&) noexcept = default;
task& operator=(task&&) noexcept = default;
R operator()(Args...); // <-- (2)
};
} // namespace v1
namespace v1 {
template <class R, class... Args>
struct task<R(Args...)>::concept_t {
virtual ~concept_t() = default; // <-- (1)
virtual R invoke_(Args...) = 0; // <-- (2)
};
} // namespace v1
namespace v1 {
template <class R, class... Args>
template <class F>
struct task<R(Args...)>::model_t final : concept_t {
F _f;
template <class G = F>
model_t(G&& f) : _f{forward<G>(f)} {} // <-- (1)
R invoke_(Args... args) override { return invoke(_f, forward<Args>(args)...); } // <-- (2)
};
} // namespace v1
namespace v1 {
template <class R, class... Args>
template <class F>
task<R(Args...)>::task(F&& f) : _model{make_unique<model_t<decay_t<F>>>(forward<F>(f))} {}
template <class R, class... Args>
R task<R(Args...)>::operator()(Args... args) {
return _model->invoke_(forward<Args>(args)...);
}
} // namespace v1
{
using namespace v1;
auto p = make_unique<int>(42);
task<unique_ptr<int>()> payload{[_p = move(p)]() mutable { return move(_p); }};
cout << *payload() << "\n";
cout << (payload() == nullptr) << "\n";
}
unique_ptr<>
instead of shared_ptr<>
namespace v2 {
template <class> class task;
template <class R, class... Args>
class task<R(Args...)> {
struct concept_t;
template <class F, bool Small> struct model_t;
static constexpr size_t max_align = alignof(max_align_t);
static constexpr size_t small_size =
max(max_align * 2, sizeof(void*) * 8) - max(max_align, sizeof(concept_t*));
static constexpr concept_t empty{[](void*) noexcept {}, [](void*, void*) noexcept {}};
const concept_t* _concept{&empty};
aligned_storage_t<small_size> _model;
public:
template <class F>
task(F&&); // <-- (1)
~task(); // <-- (2)
constexpr task() noexcept = default;
task(task&&) noexcept; // <-- (3)
task& operator=(task&&) noexcept; // <-- (4)
R operator()(Args...); // <-- (5)
};
} // namespace v2
namespace v2 {
template <class R, class... Args>
struct task<R(Args...)>::concept_t {
void (*_dtor)(void*) noexcept; // <-- (2)
void (*_move)(void*, void*) noexcept; // <-- (3, 4)
R (*_invoke)(void*, Args&&...); // <-- (5)
};
} // namespace v2
namespace v2 {
template <class R, class... Args>
template <class F>
struct task<R(Args...)>::model_t<F, true> {
template <class G>
model_t(G&& f) : _f(forward<G>(f)) {}
static void _dtor(void* self) noexcept { static_cast<model_t*>(self)->~model_t(); }
static void _move(void* self, void* p) noexcept {
new (p) model_t(move(*static_cast<model_t*>(self)));
}
static R _invoke(void* self, Args&&... args) {
return invoke(static_cast<model_t*>(self)->_f, forward<Args>(args)...);
}
static constexpr concept_t vtable{_dtor, _move, _invoke};
F _f;
};
} // namespace v2
namespace v2 {
template <class R, class... Args>
template <class F>
struct task<R(Args...)>::model_t<F, false> {
template <class G>
model_t(G&& f) : _p(make_unique<F>(forward<F>(f))) {}
static void _dtor(void* self) noexcept { static_cast<model_t*>(self)->~model_t(); }
static void _move(void* self, void* p) noexcept {
new (p) model_t(move(*static_cast<model_t*>(self)));
}
static R _invoke(void* self, Args&&... args) {
return invoke(*static_cast<model_t*>(self)->_p, forward<Args>(args)...);
}
static constexpr concept_t vtable{_dtor, _move, _invoke};
unique_ptr<F> _p;
};
} // namespace v2
namespace v2 {
template <class R, class... Args>
template <class F>
task<R(Args...)>::task(F&& f) { // <-- (1)
using small_t = model_t<decay_t<F>, true>;
using large_t = model_t<decay_t<F>, false>;
using model_type = conditional_t<(sizeof(small_t) <= small_size) &&
(alignof(small_t) <= alignof(decltype(_model))),
small_t, large_t>;
new (&_model) model_type(forward<F>(f));
_concept = &model_type::vtable;
}
template <class R, class... Args>
task<R(Args...)>::~task() { // <-- (2)
_concept->_dtor(&_model);
}
} // namespace v2
namespace v2 {
template <class R, class... Args>
task<R(Args...)>::task(task&& x) noexcept : _concept{x._concept} { // <-- (3)
_concept->_move(&x._model, &_model);
}
template <class R, class... Args>
auto task<R(Args...)>::operator=(task&& x) noexcept -> task& { // <-- (4)
if (this == &x) return *this;
_concept->_dtor(&_model);
_concept = x._concept;
_concept->_move(&x._model, &_model);
return *this;
}
template <class R, class... Args>
R task<R(Args...)>::operator()(Args... args) { // <-- (5)
return _concept->_invoke(&_model, forward<Args>(args)...);
}
} // namespace v2
{
using namespace v2;
auto p = make_unique<int>(42);
task<unique_ptr<int>()> payload{[_p = move(p)]() mutable { return move(_p); }};
display(*payload());
display(payload() == nullptr);
}