Обработка ошибок с помощью (boost::)outcome: используйте в конструкторе с композицией и наследованием

В моей среде я не могу использовать исключения, поэтому мне нужно альтернативное решение для обработки ошибок. Возвращение int в качестве кода ошибки — не лучший способ в новом современном проекте, потому что этот интерфейс не позволяет возвращать другие данные.

std::expected пока недоступен; возможно, есть несколько примеров реализации, но мне нужно что-то уже проверенное и надежное.

Я оцениваю (boost::)outcome https://ned14.github.io/outcome/, и, похоже, он соответствует моим потребностям: у него понятный интерфейс, и он должен быть очень эффективным, если не используются вспомогательные пейлоады. .

Вариант использования универсального метода класса довольно прост: https://ned14.github.io/outcome/tutorial/essential/result/

Что касается использования с конструкторами, автор предлагает двухфазную конструкцию (https://ned14.github.io/outcome/tutorial/advanced/constructors/).

В учебнике не говорится о составе классов и наследовании. Следующий пример аналогичен учебному пособию.

class A {
protected: // use protected because of C class in the next example
    constexpr A() noexcept { /*...*/ }
public:
    ~A() noexcept { /*...*/ }

    A( A&& rhs ) noexcept { /*...*/ }

    A& operator=( A&& rhs ) noexcept 
    {
        this->~A();
        new(this) A( std::move( rhs ) );
        return *this;
    }

    // Remove copy ctor and assignment op
    A( const A& ) = delete;
    A& operator=( const A& ) = delete;

    /** Static member constructor */
    static result<A> A_ctor() noexcept 
    {
        // Phase 1 ctor
        A ret;
        // Phase 2 ctor
        if ( /*something goes wrong*/ ) return MyErrorCode::Error;
        return { std::move( ret ) };
    }

    void a_method() noexcept { /*...*/ }
};

template<> struct make<A>
{
    result<A> operator()() const noexcept
    {
        return A::A_ctor();
    }
};

Теперь рассмотрим класс B, содержащий класс A. Будучи защищенным от AC, следующее объявление недопустимо:

class B {
    A a_;
    ...
};

Возможно, сработает следующее:

class B {
    result<A> a_;
    constexpr B() noexcept : a_( make<A>{}() ) {}
public:
    static result<B> B_ctor() noexcept
    {
        // Phase 1 ctor
        B ret;
        // Phase 2 ctor
        if ( ret.value().a_.has_failure() ) return MyErrorCode::Error;
        if ( /*something else goes wrong*/ ) return MyErrorCode::AnotherError;
        return { std::move( ret ) };
    }

    // ...

    void b_method() noexcept
    {
        a_.value().a_method(); // <-- ugly!
        // ...
    }
};

но использовать result<A> как тип для a_ не очень приятно. Требуется использовать a_.value() везде в коде, где используется a_. Более того, если a_ используется часто, эффективность может снизиться. Есть ли другое решение?

Есть еще один темный момент с производными классами.

class C : public A {
    constexpr C() noexcept : A() { /*...*/ }
public:
    // ...

    static result<C> C_ctor() noexcept
    {
        // Phase 1 ctor
        C ret;
        // Phase 2 ctor
        // How to reuse A ctor???
        // ...
        return { std::move( ret ) };
    }
};

В C_ctor я хотел бы построить класс, начиная с A_ctor, чтобы избежать дублирования кода, например:

result<C> ret = C::A_ctor();

но нет доступной конвертации. Любая идея решить этот вопрос?

Если конструктор по умолчанию B является закрытым, можете ли вы просто удалить его? Сохраните A непосредственно в B и создайте конструктор B::B(A&&), который вы вызываете только в случае успеха A_ctor.

user2407038 22.12.2020 22:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
458
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Следующее объявление недопустимо, так как оно защищено от ctor.

Вы действительно не можете использовать недоступные конструкторы, но конструктор перемещения является общедоступным, поэтому вы можете написать свой X_ctor по-другому:

B(A&& a) noexcept : a_(std::move(a)) {} // use public A(A&&)

static result<B> B_ctor() noexcept
{
    result<A> a = make<A>(); 
    if ( a.has_failure() ) return MyErrorCode::Error;

    // Phase 1 ctor
    B ret(std::move(a.value()));

    // Phase 2 ctor
    if ( /*something else goes wrong*/ ) return MyErrorCode::AnotherError;
    return { std::move( ret ) };
}

В C_ctor я хотел бы построить класс, начиная с A_ctor, чтобы избежать дублирования кода.

У вас могут быть init функции:

result<bool> init() noexcept 
{
    // ...
    if ( /*something goes wrong*/ ) return MyErrorCode::Error;
    return {true};
}

static result<A> A_ctor() noexcept 
{
    // Phase 1 ctor
    A ret;
    // Phase 2 ctor
    result<bool> a_init = ret.init();
    if ( a_init.has_failure() ) return a_init.error();
    return { std::move( ret ) };
}

и

result<bool> init() noexcept 
{
    result<bool> a_init = A::init();
    if ( a_init.has_failure() ) return a_init.error();

    // ...
    if ( /*something goes wrong*/ ) return MyErrorCode::Error;
    return {true};
}

static result<C> C_ctor() noexcept
{
    // Phase 1 ctor
    C ret;

    // Phase 2 ctor
    result<bool> c_init = ret.init();
    if ( c_init.has_failure() ) return c_init.error();
    return { std::move( ret ) };
}

Я воспользовался вашим советом, @Jarod42, и, похоже, он сработал. Я сомневаюсь в интерфейсе для создания объектов в куче (см. create_ptr статический ctor и make_ptr шаблон). Есть ли более изящные и эффективные решения?

Emanuele 29.12.2020 10:02

Следуя совету @Jarod42, я решил использовать следующую архитектуру.

Базовый класс:

class A {
public:
    /** Static ctor */
    static inline result<A> create( size_t size ) noexcept
    {
        if ( size <= 0 ) return CncErrorCode::InvalidData;
        // Phase 1
        A ret;
        // Phase 2
        // ...
        if ( /* some error */ ) return CncErrorCode::GenericError;
        /*.*/
        return { std::move( ret ) };
    }

    /** Static ctor */
    static inline result<A*> create_ptr( size_t size ) noexcept
    {
        if ( size <= 0 ) return CncErrorCode::InvalidData;
        // Phase 1
        A* ret = new(std::nothrow) A();
        if ( ret == nullptr ) return CncErrorCode::NoMemory;
        // Phase 2
        // ...
        if ( /* some error */ ) return CncErrorCode::GenericError;
        /*.*/
        return ret;
    }
private:
    // Some private data here
private:
    constexpr A() noexcept { /* ... */ }
public:
    ~A() noexcept { /* ... */ }

    /** Move ctor */
    constexpr A( A&& rhs ) noexcept { /* ... */ }

    /** Move op */
    A& operator=( A&& rhs ) noexcept
    {
        if ( this != &rhs )
        {
            this->~A();
            new( this ) A( std::move( rhs ) );
        }
        return *this;
    }

    // Remove copy ctor and assignment op
    A( const A& ) = delete;
    A& operator=( const A& ) = delete;
public:
    // public interface here
};

template<> struct make<A>
{
    size_t _size;

    result<A> operator()() const noexcept
    {
        return A::create( _size );
    }
};

template<> struct make_ptr<A>
{
    size_t _size;

    result<A*> operator()() const noexcept
    {
        return A::create_ptr( _size );
    }
};

Пример использования композиции:

class B {
public:
    /** Static ctor */
    static result<B> create( size_t size ) noexcept
    {
        result<A> a = make<A>{ size }();
        if ( !a ) return a.as_failure();
        /*.*/
        return { std::move( B( std::move( a.value() ) ) ) };
    }

    /** Static ctor */
    static result<B*> create_ptr( size_t size ) noexcept
    {
        result<A> a = make<A>{ size }( );
        if ( !a ) return a.as_failure();
        B* ret = new(std::nothrow) B( std::move( a.value() ) );
        if ( ret == nullptr ) return CncErrorCode::NoMemory;
        /*.*/
        return ret;
    }
private:
    A a_;
private:
    constexpr B( A&& a ) noexcept : a_( std::move( a ) ) {}
public:
    /** Move ctor */
    constexpr B( B&& rhs ) noexcept : a_( std::move( rhs.a_ ) ) {}

    /** Move op */
    B& operator=( B&& rhs ) noexcept
    {
        if ( this != &rhs )
        {
            this->~B();
            new( this ) B( std::move( rhs ) );
        }
        return *this;
    }

    // Remove copy ctor and assignment op
    B( const B& ) = delete;
    B& operator=( const B& ) = delete;
};

template<> struct make<B>
{
    size_t _size;

    result<B> operator()() const noexcept
    {
        return B::create( _size );
    }
};

template<> struct make_ptr<B>
{
    size_t _size;

    result<B*> operator()() const noexcept
    {
        return B::create_ptr( _size );
    }
};

Вариант использования наследования:

class C : public A {
public:
    /** Static ctor */
    static result<C> create( size_t size ) noexcept
    {
        result<A> base = make<A>{ size }();
        if ( !base ) return base.as_failure();
        /*.*/
        return { std::move( C( std::move( base.value() ) ) ) };
    }

    /** Static ctor */
    static result<C*> create_ptr( size_t size ) noexcept
    {
        result<A> base = make<A>{ size }();
        if ( !base ) return base.as_failure();
        C* ret = new(std::nothrow) C( std::move( base.value() ) );
        if ( ret == nullptr ) return CncErrorCode::NoMemory;
        /*.*/
        return ret;
    }
private:
    constexpr C( A&& base ) noexcept : A( std::move( base ) ) {}
public:
    /** Move ctor */
    constexpr C( C&& rhs ) noexcept : A( std::move( rhs ) ) {}

    /** Move op */
    C& operator=( C&& rhs ) noexcept
    {
        if ( this != &rhs )
        {
            this->~C();
            new( this ) C( std::move( rhs ) );
        }
        return *this;
    }

    // Remove copy ctor and assignment op
    C( const C& ) = delete;
    C& operator=( const C& ) = delete;
};

template<> struct make<C>
{
    size_t _size;

    result<C> operator()() const noexcept
    {
        return C::create( _size );
    }
};

template<> struct make_ptr<C>
{
    size_t _size;

    result<C*> operator()() const noexcept
    {
        return C::create_ptr( _size );
    }
};

Использование:

auto b_r = make<B>{21}();
if ( b_r.has_error() )
{
    if ( b_r.error() == CncErrorCode::NoMemory ) { /* ... */ }
    else { /* ... */ }
}
B& b = b_r.value(); // Get an alias to the value
// ...

auto c_r = make_ptr<C>{53}();
if ( c_r.has_error() ) { /* ... */ }
C* c = c_r.value(); // Get an alias to the value
// ...
delete c;

Другие вопросы по теме