errno:
在C++标准库中被定义为预处理宏.如果想要使用需要#include<cerrno>.
是一套POSIX error codes用于获取当前线程运行时errors因此可以理解为errno为一个具有thread_local 生命周期的变量.
首先明确什么是Not all errors are exceptional:
例如: 在network-program中:
- You were unable to connect to a remote IP address.
- Your connection dropped out.
- You tried to open an IPv6 socket but no IPv6 network interfaces are available.
这些都是可能发生的情况因此并不一定非要throw.我们可以通过errno的值来进行判断,进行对各种情况的处理.
demo1:
#include <iostream>
#include <cmath>
#include <cerrno>
#include <cstring>
#include <clocale>
#include <cassert>
#include <thread>
#include <cmath>
#include <mutex>
std::mutex global_mutex;
//notice that: get the log of value which is less than 0;
void function(const double& value)noexcept
{
std::lock_guard<std::mutex> lg{ global_mutex };
std::cout << value << std::endl;
std::log(value);
std::cout << "errno: " << errno << std::endl;
}
int main()
{
std::thread t1{ function, -1.0 };
std::thread t2{ function, 10.0 };
t1.join();
t2.join();
return 0;
}
demo2:
#include <iostream>
#include <cmath>
#include <cerrno>
#include <cstring>
#include <clocale>
int main()
{
double not_a_number = std::log(-1.0);
if (errno == EDOM) {
std::cout << "some logic error occur!" << std::endl;
}
return 0;
}
由于POSIX中的错误码可能不够用或者有的库作者不愿意使用于是就自己弄了一套自己的:
例如:
举一个例子,getaddrinfo() 族的函数使用独立的一套错误码(EAI_* ,有别于 POSIX 的 E* 错误码),但是(这些状态码)依然和 GetLastError() 处于同一套命名空间当中。SSL 使用另一套错误码.
Windows 的 GetLastError() 那堆是一套(WSA* 也在这里),POSIX 的 E* 是一套,SSL 的又是一套。
感谢@lh_mouse的指点.
于是自打C++11以来标准库提供了<system_error>来改善这种情况其指导思想为:
1),Not all errors are exceptional(不是所有的错误都是异常)
2),Errors come from multiple sources
3),Be user-extensible
4),Preserve the original error code
class error_category: 作为具体的error category的基类,比如std::system_category/std::iostream_category均继承自class error_category. 特别重要的是每个class error_category或者它的派生类提供class error_code和class error_condition之间的相互映射,同时class error_category或者它的派生类还提供对class error_condition的描述.
class error_code: 依赖平台(platform-dependent)的且在不同的category的class error_code下面的值(error code)是可以一样的, 每个class error_code object由一个error code和一个class error_category或者class error_category的派生类组成.
class error_condition: 不依赖平台(platform-independent)的error code(这里的error code就只是代表错误码,必须唯一的),同样每个class error_condtion object由一个 error code和一个class error_category或者class error_category的派生类组成.
enum class erroc: 一组通用的错误条件值,从POSIX派生而来。
接着让我们看一个例子
demo2:
void create_directory(
const std::string& pathname,
std::error_code& ec);
std::error_code ec;
create_directory("/some/path", ec);
这个函数调用可能因为一下情况失败:
- The directory already exists.
- The path is too long.
- The parent path doesn't exist.
如果只是想简单的判断是否创建成功下面这些是足够了的:
std::error_code ec;
create_directory("/some/path", ec);
if (!ec)
{
// Success.
}
else
{
// Failure.
}
但是假设我们想要细化一下错误的具体类型到底是哪种导致的呢?
std::error_code ec;
create_directory("/some/path", ec);
if (ec.value() == EEXIST) // No!
...
这种情况下在POSIX platform的平台上面跑是没问题的但是如果并不是完全遵循POSIX的平台比如微软,这么写绝对是不对的,比如微软平台的是 ERROR_ALREADY_EXISTS 才对应上文的EEXIST.
于是C++0x以来解决了这个问题我们可以这样:
std::error_code ec;
create_directory("/some/path", ec);
if (ec == std::errc::file_exists)
...
这样是没有问题的因为C++0x以来标准库实现了对 EEXIST or ERROR_ALREADY_EXISTS 到std::errc::file_exists转换的封装.
其中在demo2中:
if (ec == std::errc::file_exists)
虽然只是简单的一句 operator== 调用但是背后远远没有我们想象的那么简单.
Step1: 判断 enum(例如demo2中的 std::errc::file_exists)是属于class error_code还是class error_condition
具体判断的实现如下,标准库中把位于std::errc则个enum class归与class error_condition比较,然后由于operator== 提供了class error_code和class error_condition之间的比较但是我们比较的却是一个enum类型于是这里也涉及到一个隐式的转换.
这里的隐式转换涉及到 SFINAE:
因为std::errc既能转为class error_condition又能转为class error_code.
namespace std{
template <class T>
struct is_error_code_enum
: public false_type {};
template <class T>
struct is_error_condition_enum
: public false_type {};
template <>
struct is_error_condition_enum<errc>
: true_type {};
bool operator==(const error_condition& lhs, const error_condition& rhs) noexcept;
bool operator==(const error_code& lhs, const error_condition& rhs) noexcept;
bool operator==(const error_condition& lhs, const error_code& rhs) noexcept;
class error_condition
{
template <class ErrorCodeEnum, typename = typename enable_if<is_error_code_enum<ErrorCodeEnum>::value, void>::type>
error_condition(ErrorConditionEnum e);//notice that: the enum value can change inexplicitly to std::error_condition
{
*this = make_error_condition(e);
}
};
class error_code
{
template <class ErrorCodeEnum, typename = typename enable_if<is_error_condition_enum<ErrorCodeEnum>::value, void>::type>
error_code(ErrorCodeEnum e); //notice that: the enum value can change inexplicitly to std::error_code
{
*this = make_error_code(e);
}
};
}
看了上面的肯定还是不太明白class error_category去哪了?
class error_category做为其中重要的一环,用来指示该错误所属的category,既然有疑惑让我们接着往下看:
namespace std{
inline error_condition make_error_condition(std::errc _Errno) noexcept
{ // make an error_condition
return (error_condition((int)_Errno, generic_category()));
}
inline error_code make_error_code(std::errc _Errno) noexcept
{ // make an error_code
return (error_code((int)_Errno, generic_category()));
}
}
其中的std::generic_category()又再次带给了我们新的问题这个函数是干什么的呢?
这个函数并不是提供给库的使用者使用的,但是又非常的重要该函数的实现必须保证每次返回的都是对同一个object的引用,在operator==比较的过程中class error_category也要参与比较:
namespace{
struct generic_category : public std::error_category
{
virtual const char*
name() const noexcept
{ return "generic"; }
virtual string
message(int i) const
{
return string(std::strerror(i)); //其实类似与std::to_string(int number);
}
};
}
namespace std{
inline const error_category& generic_category()
{
static generic_category instance;
return instance;
}
}
在 if (ec == std::errc::file_exists) 中经过上述一路波折终于 std::errc::file_exisits 转为了 std::error_condtion,接着就是 std::error_code == std::error_condtion比较的问题了,标准库提供了以下几个重载函数:
namespace std{
inline bool
operator==(const error_code& __lhs, const error_code& __rhs) noexcept
{ return (__lhs.category() == __rhs.category()
&& __lhs.value() == __rhs.value()); }
inline bool
operator==(const error_code& __lhs, const error_condition& __rhs) noexcept
{
return (__lhs.category().equivalent(__lhs.value(), __rhs)
|| __rhs.category().equivalent(__lhs, __rhs.value()));
}
inline bool
operator==(const error_condition& __lhs, const error_code& __rhs) noexcept
{
return (__rhs.category().equivalent(__rhs.value(), __lhs)
|| __lhs.category().equivalent(__rhs, __lhs.value()));
}
inline bool
operator==(const error_condition& __lhs,
const error_condition& __rhs) noexcept
{
return (__lhs.category() == __rhs.category()
&& __lhs.value() == __rhs.value());
}
}
以我们的上面的比较为例子这里会调用这个函数:
inline bool
operator==(const error_code& __lhs, const error_condition& __rhs) noexcept
{
return (__lhs.category().equivalent(__lhs.value(), __rhs)
|| __rhs.category().equivalent(__lhs, __rhs.value()));
}
我们再次看不懂了怎么办呢?
那么我们就来解析一下class error_code和class error_condition:
class error_code(GCC 6.3 Debian):
struct error_code
{
//default-constructor!
error_code() noexcept
: _M_value(0), _M_cat(&system_category()) { }
//construct form a int value and a category!
error_code(int __v, const error_category& __cat) noexcept
: _M_value(__v), _M_cat(&__cat) { }
//constructor from a enum class value.
template<typename _ErrorCodeEnum, typename = typename
enable_if<is_error_code_enum<_ErrorCodeEnum>::value>::type>
error_code(_ErrorCodeEnum __e) noexcept
{ *this = make_error_code(__e); }
//接受一个int值和一个category.
void
assign(int __v, const error_category& __cat) noexcept
{
_M_value = __v;
_M_cat = &__cat;
}
//设置为默认构造函数构造后的状态.
void
clear() noexcept
{ assign(0, system_category()); }
//!!!!!!!!!!!!!!!!!!!!!!!!!!!
//这里需要特别的注意: 这是一个template operator=(俗称: 泛型拷贝赋值运算符)
//它并不会影响编译器合成 std::error_code::operator=(const std::error_code& other)noexcept.
template<typename _ErrorCodeEnum>
typename enable_if<is_error_code_enum<_ErrorCodeEnum>::value,
error_code&>::type
operator=(_ErrorCodeEnum __e) noexcept
{ return *this = make_error_code(__e); }
int
value() const noexcept { return _M_value; }
const error_category&
category() const noexcept { return *_M_cat; }
error_condition
error_code::default_error_condition() const noexcept
{ return category().default_error_condition(value()); }
string
message() const
{ return category().message(value()); }
explicit operator bool() const noexcept
{ return _M_value != 0; }
// DR 804.
private:
int _M_value;
const error_category* _M_cat;
};
class error_condition(GCC 6.3 Debian):
struct error_condition
{
//default-constructor!
error_condition() noexcept
: _M_value(0), _M_cat(&generic_category()) { }
//construct form a int value and a category(or inherit form std::error_category).
error_condition(int __v, const error_category& __cat) noexcept
: _M_value(__v), _M_cat(&__cat) { }
//construct form enum class.
template<typename _ErrorConditionEnum, typename = typename
enable_if<is_error_condition_enum<_ErrorConditionEnum>::value>::type>
error_condition(_ErrorConditionEnum __e) noexcept
{ *this = make_error_condition(__e); }
void
assign(int __v, const error_category& __cat) noexcept
{
_M_value = __v;
_M_cat = &__cat;
}
//!!!!!!!!!!!!!!!!!!!
//这里的: 泛化operator=跟上面的 std::error_code一样
//不会影响编译器产生std::error_condition::operator=(const std::error_condition& other)noexcept;
template<typename _ErrorConditionEnum>
typename enable_if<is_error_condition_enum
<_ErrorConditionEnum>::value, error_condition&>::type
operator=(_ErrorConditionEnum __e) noexcept
{ return *this = make_error_condition(__e); }
void
clear() noexcept
{ assign(0, generic_category()); }
// 19.4.3.4 observers
int
value() const noexcept { return _M_value; }
const error_category&
category() const noexcept { return *_M_cat; }
_GLIBCXX_DEFAULT_ABI_TAG
string
message() const
{ return category().message(value()); }
explicit operator bool() const noexcept
{ return _M_value != 0; }
// DR 804.
private:
int _M_value;
const error_category* _M_cat;
};
class error_category(GCC 6.3 Debian):
namespace std{
class error_category
{
public:
constexpr error_category() noexcept = default;
virtual ~error_category();
error_category(const error_category&) = delete;
error_category& operator=(const error_category&) = delete;
//pure-virtual function!
virtual const char*
name() const noexcept = 0;
//pure-virtual function!
virtual string
message(int) const = 0;
//virtual function
virtual
error_condition default_error_condition(int __i) const noexcept
{
if (*this == system_category())
return error_condition(__i, system_category());
return error_condition(__i, generic_category());
}
//virtual function
virtual
bool equivalent(int __i, const error_condition& __cond) const noexcept
{ return default_error_condition(__i) == __cond; }
//virtual function
virtual
bool equivalent(const error_code& __code, int __i) const noexcept
{
if (*this == system_category()
&& __code.category() == system_category())
return __code.value() == __i;
if (*this == generic_category()
&& __code.category() == generic_category())
return __code.value() == __i;
return false;
}
bool
operator<(const error_category& __other) const noexcept
{ return less<const error_category*>()(this, &__other); }
bool
operator==(const error_category& __other) const noexcept
{ return this == &__other; }
bool
operator!=(const error_category& __other) const noexcept
{ return this != &__other; }
};
}
结合上面的:
std::error_code ec;
create_directory("/some/path", ec);
if (ec == std::errc::file_exists)
...
我们假设调用careate_directory("/some/path", ec)的时候:
ec.assign(static_cast<int>(std::errc::file_exists), generic_category());
这里的std::errc::file_exists转为了:
std::error_condition{std::error::file_exists};
最终的的调用为:
inline bool
operator==(const error_code& __lhs, const error_condition& __rhs) noexcept
{
return (__lhs.category().equivalent(__lhs.value(), __rhs)
|| __rhs.category().equivalent(__lhs, __rhs.value()));
}
其中在_lhs.category().equivalent(_lsh.value(), rhs);调用的时候内部映射为:
//step1:
//virtual function
virtual
bool equivalent(int __i, const error_condition& __cond) const noexcept
{ return default_error_condition(__i) == __cond; }
//step2:
virtual
error_condition default_error_condition(int __i) const noexcept
{
if (this == &(system_category()))//notice that: compare two pointers.
return error_condition(__i, system_category());
return error_condition(__i, generic_category());
}
//step3:
inline bool
operator==(const error_condition& __lhs,
const error_condition& __rhs) noexcept
{
return (&(__lhs.category()) == &(__rhs.category()) //notice that: compare two pointers.
&& __lhs.value() == __rhs.value());
}