В моей среде я не могу использовать исключения, поэтому мне нужно альтернативное решение для обработки ошибок. Возвращение 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();
но нет доступной конвертации. Любая идея решить этот вопрос?
Следующее объявление недопустимо, так как оно защищено от 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
шаблон). Есть ли более изящные и эффективные решения?
Следуя совету @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;
Если конструктор по умолчанию
B
является закрытым, можете ли вы просто удалить его? СохранитеA
непосредственно вB
и создайте конструкторB::B(A&&)
, который вы вызываете только в случае успехаA_ctor
.