使用 (boost::)outcome 进行错误处理:在具有组合和继承的构造函数中使用
Error handling with (boost::)outcome : use in constructor with composition and inheritance
在我的环境中,我不能使用异常,所以我需要一个替代的错误处理解决方案。在新的现代项目中返回一个 int 作为错误代码不是一个好方法,因为这个接口阻止 return 其他数据。
std::expected 尚不可用;也许有一些示例实现,但我需要一些已经过测试且健壮的东西。
我正在评估 (boost::)outcome https://ned14.github.io/outcome/ 它似乎符合我的需求:它有一个清晰的界面,如果不使用辅助有效载荷,它应该非常有效。
泛型 class 方法的用例非常简单:https://ned14.github.io/outcome/tutorial/essential/result/
关于constructors的用法,作者建议双相构造(https://ned14.github.io/outcome/tutorial/advanced/constructors/)。
本教程不讨论 class 组合和继承。以下示例与教程相同
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();
}
};
现在考虑包含 class A 的 class B。受 A 构造函数保护,以下声明无效:
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_
的代码中的任何地方使用 a_.value()
。此外,如果经常使用a_
,效率可能会降低。还有其他解决办法吗?
派生的classes还有另一个暗点。
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
开始构建 class 以避免代码重复,例如:
result<C> ret = C::A_ctor();
但是没有可用的转换。有解决这个问题的想法吗?
Being A ctor protected, the following declaration is not valid.
您确实不能使用不可访问的构造函数,但是移动构造函数是 public,
所以你可以用不同的方式写 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 ) };
}
In C_ctor I would like to construct the class starting from A_ctor to avoid code duplication
您可能有 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 的建议,我用以下架构解决了问题。
基础class:
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;
在我的环境中,我不能使用异常,所以我需要一个替代的错误处理解决方案。在新的现代项目中返回一个 int 作为错误代码不是一个好方法,因为这个接口阻止 return 其他数据。
std::expected 尚不可用;也许有一些示例实现,但我需要一些已经过测试且健壮的东西。
我正在评估 (boost::)outcome https://ned14.github.io/outcome/ 它似乎符合我的需求:它有一个清晰的界面,如果不使用辅助有效载荷,它应该非常有效。
泛型 class 方法的用例非常简单:https://ned14.github.io/outcome/tutorial/essential/result/
关于constructors的用法,作者建议双相构造(https://ned14.github.io/outcome/tutorial/advanced/constructors/)。
本教程不讨论 class 组合和继承。以下示例与教程相同
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();
}
};
现在考虑包含 class A 的 class B。受 A 构造函数保护,以下声明无效:
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_
的代码中的任何地方使用 a_.value()
。此外,如果经常使用a_
,效率可能会降低。还有其他解决办法吗?
派生的classes还有另一个暗点。
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
开始构建 class 以避免代码重复,例如:
result<C> ret = C::A_ctor();
但是没有可用的转换。有解决这个问题的想法吗?
Being A ctor protected, the following declaration is not valid.
您确实不能使用不可访问的构造函数,但是移动构造函数是 public,
所以你可以用不同的方式写 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 ) };
}
In C_ctor I would like to construct the class starting from A_ctor to avoid code duplication
您可能有 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 的建议,我用以下架构解决了问题。
基础class:
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;