Created
October 2, 2023 10:17
-
-
Save NTG-TPL/bc64888287567aabda3dc6918785c836 to your computer and use it in GitHub Desktop.
Simple C++ Implementation std::optional
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdexcept> | |
#include <utility> | |
// Исключение этого типа должно генерироватся при обращении к пустому optional | |
class BadOptionalAccess : public std::exception { | |
public: | |
using exception::exception; | |
virtual const char* what() const noexcept override { | |
return "Bad optional access"; | |
} | |
}; | |
template <typename T> | |
class Optional { | |
public: | |
Optional() = default; | |
Optional(const T& value){ | |
ptr_ = new (data_) T(value); | |
is_initialized_ = true; | |
} | |
Optional(T&& value){ | |
ptr_ = new (data_) T(std::move(value)); | |
is_initialized_ = true; | |
} | |
Optional(const Optional& other){ | |
if (other.HasValue()){ | |
ptr_ = new (data_) T(*other); | |
is_initialized_ = true; | |
} | |
} | |
Optional(Optional&& other){ | |
if (other.HasValue()){ | |
ptr_ = new (data_) T(std::move(*other)); | |
is_initialized_ = true; | |
} | |
} | |
Optional& operator=(const T& value){ | |
if (HasValue()){ | |
*ptr_ = value; | |
} | |
else{ | |
ptr_ = new (data_) T(value); | |
} | |
is_initialized_ = true; | |
return *this; | |
} | |
Optional& operator=(T&& rhs){ | |
if (HasValue()){ | |
*ptr_ = std::move(rhs); | |
} | |
else{ | |
ptr_ = new (data_) T(std::move(rhs)); | |
} | |
is_initialized_ = true; | |
return *this; | |
} | |
Optional& operator=(const Optional& rhs){ | |
if (rhs.HasValue()){ | |
if (HasValue()){ | |
*ptr_ = *rhs; | |
} | |
else{ | |
ptr_ = new (data_) T(*rhs); | |
} | |
is_initialized_ = true; | |
} | |
else { | |
Reset(); | |
} | |
return *this; | |
} | |
template<typename... Args> | |
void Emplace(Args&&... args){ | |
if(is_initialized_){ | |
Reset(); | |
} | |
ptr_ = new (data_) T(std::forward<Args>(args)...); | |
is_initialized_ = true; | |
} | |
Optional& operator=(Optional&& rhs){ | |
if (rhs.HasValue()){ | |
if (HasValue()){ | |
*ptr_ = std::move(*rhs); | |
} | |
else{ | |
ptr_ = new (data_) T(std::move(*rhs)); | |
} | |
is_initialized_ = true; | |
} | |
else { | |
Reset(); | |
} | |
return *this; | |
} | |
~Optional(){ | |
Reset(); | |
} | |
bool HasValue() const{ | |
return is_initialized_; | |
} | |
T& operator*() &{ | |
return *ptr_; | |
} | |
const T& operator*() const &{ | |
return *ptr_; | |
} | |
T&& operator*() &&{ | |
return std::move(*ptr_); | |
} | |
T* operator->(){ | |
return ptr_; | |
} | |
const T* operator->() const{ | |
return ptr_; | |
} | |
T& Value() &{ | |
if (!is_initialized_){ | |
throw BadOptionalAccess{}; | |
} | |
return *ptr_; | |
} | |
const T& Value() const &{ | |
if (!is_initialized_){ | |
throw BadOptionalAccess{}; | |
} | |
return *ptr_; | |
} | |
T&& Value() &&{ | |
if (!is_initialized_){ | |
throw BadOptionalAccess{}; | |
} | |
return std::move(*ptr_); | |
} | |
void Reset(){ | |
if (is_initialized_){ | |
ptr_->~T(); | |
ptr_ = nullptr; | |
is_initialized_ = false; | |
} | |
} | |
private: | |
// alignas нужен для правильного выравнивания блока памяти | |
alignas(T) char data_[sizeof(T)]; | |
T * ptr_ = nullptr; | |
bool is_initialized_ = false; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "optional.h" | |
#include <iostream> | |
#include <cassert> | |
#include <memory> | |
struct C { | |
C() noexcept { | |
++def_ctor; | |
} | |
C(const C& /*other*/) noexcept { | |
++copy_ctor; | |
} | |
C(C&& /*other*/) noexcept { | |
++move_ctor; | |
} | |
C& operator=(const C& other) noexcept { | |
if (this != &other) { | |
++copy_assign; | |
} | |
return *this; | |
} | |
C& operator=(C&& /*other*/) noexcept { | |
++move_assign; | |
return *this; | |
} | |
~C() { | |
++dtor; | |
} | |
void Update() const& { | |
++const_lvalue_call_count; | |
} | |
void Update() & { | |
++lvalue_call_count; | |
} | |
void Update() && { | |
++rvalue_call_count; | |
} | |
static size_t InstanceCount() { | |
return def_ctor + copy_ctor + move_ctor - dtor; | |
} | |
static void Reset() { | |
def_ctor = 0; | |
copy_ctor = 0; | |
move_ctor = 0; | |
copy_assign = 0; | |
move_assign = 0; | |
dtor = 0; | |
lvalue_call_count = 0; | |
rvalue_call_count = 0; | |
const_lvalue_call_count = 0; | |
} | |
inline static size_t def_ctor = 0; | |
inline static size_t copy_ctor = 0; | |
inline static size_t move_ctor = 0; | |
inline static size_t copy_assign = 0; | |
inline static size_t move_assign = 0; | |
inline static size_t dtor = 0; | |
inline static size_t lvalue_call_count = 0; | |
inline static size_t rvalue_call_count = 0; | |
inline static size_t const_lvalue_call_count = 0; | |
}; | |
void TestInitialization() { | |
C::Reset(); | |
{ | |
Optional<C> o; | |
assert(!o.HasValue()); | |
assert(C::InstanceCount() == 0); | |
} | |
assert(C::InstanceCount() == 0); | |
C::Reset(); | |
{ | |
C c; | |
Optional<C> o(c); | |
assert(o.HasValue()); | |
assert(C::def_ctor == 1 && C::copy_ctor == 1); | |
assert(C::InstanceCount() == 2); | |
} | |
assert(C::InstanceCount() == 0); | |
C::Reset(); | |
{ | |
C c; | |
Optional<C> o(std::move(c)); | |
assert(o.HasValue()); | |
assert(C::def_ctor == 1 && C::move_ctor == 1 && C::copy_ctor == 0 && C::copy_assign == 0 | |
&& C::move_assign == 0); | |
assert(C::InstanceCount() == 2); | |
} | |
assert(C::InstanceCount() == 0); | |
C::Reset(); | |
{ | |
C c; | |
Optional<C> o1(c); | |
const Optional<C> o2(o1); | |
assert(o1.HasValue()); | |
assert(o2.HasValue()); | |
assert(C::def_ctor == 1 && C::move_ctor == 0 && C::copy_ctor == 2 && C::copy_assign == 0 | |
&& C::move_assign == 0); | |
assert(C::InstanceCount() == 3); | |
} | |
assert(C::InstanceCount() == 0); | |
C::Reset(); | |
{ | |
C c; | |
Optional<C> o1(c); | |
const Optional<C> o2(std::move(o1)); | |
assert(C::def_ctor == 1 && C::copy_ctor == 1 && C::move_ctor == 1 && C::copy_assign == 0 | |
&& C::move_assign == 0); | |
assert(C::InstanceCount() == 3); | |
} | |
assert(C::InstanceCount() == 0); | |
} | |
void TestAssignment() { | |
Optional<C> o1; | |
Optional<C> o2; | |
{ // Assign a value to empty | |
C::Reset(); | |
C c; | |
o1 = c; | |
assert(C::def_ctor == 1 && C::copy_ctor == 1 && C::dtor == 0); | |
} | |
{ // Assign a non-empty to empty | |
C::Reset(); | |
o2 = o1; | |
assert(C::copy_ctor == 1 && C::copy_assign == 0 && C::dtor == 0); | |
} | |
{ // Assign non-empty to non-empty | |
C::Reset(); | |
o2 = o1; | |
assert(C::copy_ctor == 0 && C::copy_assign == 1 && C::dtor == 0); | |
} | |
{ // Assign empty to non-empty | |
C::Reset(); | |
Optional<C> empty; | |
o1 = empty; | |
assert(C::copy_ctor == 0 && C::dtor == 1); | |
assert(!o1.HasValue()); | |
} | |
} | |
void TestMoveAssignment() { | |
{ // Assign a value to empty | |
Optional<C> o1; | |
C::Reset(); | |
C c; | |
o1 = std::move(c); | |
assert(C::def_ctor == 1 && C::move_ctor == 1 && C::dtor == 0); | |
} | |
{ // Assign a non-empty to empty | |
Optional<C> o1; | |
Optional<C> o2{C{}}; | |
C::Reset(); | |
o1 = std::move(o2); | |
assert(C::move_ctor == 1 && C::move_assign == 0 && C::dtor == 0); | |
} | |
{ // Assign non-empty to non-empty | |
Optional<C> o1{C{}}; | |
Optional<C> o2{C{}}; | |
C::Reset(); | |
o2 = std::move(o1); | |
assert(C::copy_ctor == 0 && C::move_assign == 1 && C::dtor == 0); | |
} | |
{ // Assign empty to non-empty | |
Optional<C> o1{C{}}; | |
C::Reset(); | |
Optional<C> empty; | |
o1 = std::move(empty); | |
assert(C::copy_ctor == 0 && C::move_ctor == 0 && C::move_assign == 0 && C::dtor == 1); | |
assert(!o1.HasValue()); | |
} | |
} | |
void TestValueAccess() { | |
using namespace std::literals; | |
{ | |
Optional<std::string> o; | |
o = "hello"s; | |
assert(o.HasValue()); | |
assert(o.Value() == "hello"s); | |
assert(&*o == &o.Value()); | |
assert(o->length() == 5); | |
} | |
{ | |
try { | |
Optional<int> o; | |
[[maybe_unused]] int v = o.Value(); | |
assert(false); | |
} catch (const BadOptionalAccess& /*e*/) { | |
} catch (...) { | |
assert(false); | |
} | |
} | |
} | |
void TestReset() { | |
C::Reset(); | |
{ | |
Optional<C> o{C()}; | |
assert(o.HasValue()); | |
o.Reset(); | |
assert(!o.HasValue()); | |
} | |
} | |
void TestEmplace() { | |
struct S { | |
S(int i, std::unique_ptr<int>&& p) | |
: i(i) | |
, p(std::move(p)) // | |
{ | |
} | |
int i; | |
std::unique_ptr<int> p; | |
}; | |
Optional<S> o; | |
o.Emplace(1, std::make_unique<int>(2)); | |
assert(o.HasValue()); | |
assert(o->i == 1); | |
assert(*(o->p) == 2); | |
o.Emplace(3, std::make_unique<int>(4)); | |
assert(o.HasValue()); | |
assert(o->i == 3); | |
assert(*(o->p) == 4); | |
} | |
void TestRefQualifiedMethodOverloading() { | |
{ | |
C::Reset(); | |
C val = *Optional<C>(C{}); | |
assert(C::copy_ctor == 0); | |
assert(C::move_ctor == 2); | |
assert(C::def_ctor == 1); | |
assert(C::copy_assign == 0); | |
assert(C::move_assign == 0); | |
} | |
{ | |
C::Reset(); | |
C val = Optional<C>(C{}).Value(); | |
assert(C::copy_ctor == 0); | |
assert(C::move_ctor == 2); | |
assert(C::def_ctor == 1); | |
assert(C::copy_assign == 0); | |
assert(C::move_assign == 0); | |
} | |
{ | |
C::Reset(); | |
Optional<C> opt(C{}); | |
(*opt).Update(); | |
assert(C::lvalue_call_count == 1); | |
assert(C::rvalue_call_count == 0); | |
(*std::move(opt)).Update(); | |
assert(C::lvalue_call_count == 1); | |
assert(C::rvalue_call_count == 1); | |
} | |
{ | |
C::Reset(); | |
const Optional<C> opt(C{}); | |
(*opt).Update(); | |
assert(C::const_lvalue_call_count == 1); | |
} | |
{ | |
C::Reset(); | |
Optional<C> opt(C{}); | |
opt.Value().Update(); | |
assert(C::lvalue_call_count == 1); | |
assert(C::rvalue_call_count == 0); | |
std::move(opt).Value().Update(); | |
assert(C::lvalue_call_count == 1); | |
} | |
{ | |
C::Reset(); | |
const Optional<C> opt(C{}); | |
opt.Value().Update(); | |
assert(C::const_lvalue_call_count == 1); | |
} | |
} | |
int main() { | |
try { | |
TestInitialization(); | |
TestAssignment(); | |
TestMoveAssignment(); | |
TestValueAccess(); | |
TestReset(); | |
TestEmplace(); | |
TestRefQualifiedMethodOverloading(); | |
std::cerr << "Tests Completed !!!" << std::endl; | |
} catch (...) { | |
assert(false); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment