Skip to content

Instantly share code, notes, and snippets.

@NTG-TPL
Created October 2, 2023 10:17
Show Gist options
  • Save NTG-TPL/bc64888287567aabda3dc6918785c836 to your computer and use it in GitHub Desktop.
Save NTG-TPL/bc64888287567aabda3dc6918785c836 to your computer and use it in GitHub Desktop.
Simple C++ Implementation std::optional
#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;
};
#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