皆さん御存知の通り、C++にはstd::error_category
という物がある。
if(/*なんか*/) throw std::system_error(std::error_code(GetLastError(), std::system_category()));
例えばstd::system_category()
はstd::error_category
から派生したクラス型のオブジェクトへの参照を返す。
おなじみcppjpの解説は
http://cpprefjp.github.io/reference/system_error/system_category.html
error_categoryクラスは、エラー情報を分類するための基本クラスである。
エラーコードから対応するエラーメッセージを取得する処理が異なる場合などで、error_categoryクラスを派生して環境固有のエラー情報を取得するためのクラスを定義できる。
となっている。
まずはデフォルトコンストラクタでも見てみるか
mingw-gcc6.1.0の場合
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
inline namespace _V2 {
class error_category
{
public:
constexpr error_category() noexcept = default;
};
} // end inline namespace
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
まあそうだよな。
VS2013の場合
_STD_BEGIN
class error_category
{ // categorize an error
public:
error_category()
{ // default constructor
}
};
_STD_END
VS2013はconstexprに対応していないからまあそうなるわな。
VS2015の場合
_STD_BEGIN
class error_category
{ // categorize an error
public:
/* constexpr */ error_category() _NOEXCEPT // TRANSITION
{ // default constructor
_Addr = reinterpret_cast<uintptr_t>(this);
}
};
_STD_END
あれ、気がついたらいつの間にか開いていた規格書ではconstexprにしろと書いてあるんだけど、なってないな・・・。
・・・ん?_Addr
ってなんだ。
VS2015の実装を全部見てみる。
_STD_BEGIN
class error_category
{ // categorize an error
public:
/* constexpr */ error_category() _NOEXCEPT // TRANSITION
{ // default constructor
_Addr = reinterpret_cast<uintptr_t>(this);
}
virtual ~error_category() _NOEXCEPT
{ // destroy the object
}
virtual const char *name() const _NOEXCEPT = 0;
virtual string message(int _Errval) const = 0;
virtual error_condition
default_error_condition(int _Errval) const _NOEXCEPT;
virtual bool equivalent(int _Errval,
const error_condition& _Cond) const _NOEXCEPT;
virtual bool equivalent(const error_code& _Code,
int _Errval) const _NOEXCEPT;
bool operator==(const error_category& _Right) const _NOEXCEPT
{ // compare categories for equality
return (_Addr == _Right._Addr);
}
bool operator!=(const error_category& _Right) const _NOEXCEPT
{ // compare categories for inequality
return (!(*this == _Right));
}
bool operator<(const error_category& _Right) const _NOEXCEPT
{ // compare categories for order
return (_Addr < _Right._Addr);
}
error_category(const error_category&) = delete;
error_category& operator=(const error_category&) = delete;
protected:
uintptr_t _Addr;
enum : uintptr_t
{ // imaginary addresses for Standard error_category objects
_Future_addr = 1,
_Generic_addr = 3,
_Iostream_addr = 5,
_System_addr = 7
};
};
_STD_END
どうやら比較演算子で使われているらしい。
_STD_BEGIN
class error_category
{ // categorize an error
bool operator==(const error_category& _Right) const _NOEXCEPT
{ // compare categories for equality
return (_Addr == _Right._Addr);
}
bool operator<(const error_category& _Right) const _NOEXCEPT
{ // compare categories for order
return (_Addr < _Right._Addr);
}
};
_STD_END
ちょっとまって、規格書を見に行こう。
§ 19.5.1.1
19.5.1.1 Class error_category overview [syserr.errcat.overview]
- The class
error_category
serves as a base class for types used to identify the source and encoding of a
particular category of error code. Classes may be derived fromerror_category
to support categories of
errors in addition to those defined in this International Standard. Such classes shall behave as specified in
this subclause. [ Note:error_category
objects are passed by reference, and two such objects are equal
if they have the same address. This means that applications using custom error_category types should
create a single object of each such type. —end note ]
§ 19.5.1.3
2
bool operator==(const error_category& rhs) const noexcept;
Returns: this == &rhs
.
4
bool operator<(const error_category& rhs) const noexcept;
Returns:
less<const error_category*>()(this, &rhs)
.
[ Note: less (20.9.6) provides a total ordering for pointers. —end note ]
デフォルトコンストラクタはthisポインタを保存しているみたいだけど・・・なんでそんなことを?
std::system_categoryを調べてみる
この謎をとくために、std::system_category
という関数を見てみよう。
_STD_BEGIN
template<class _Ty> inline
_Ty& _Immortalize()
{ // return a reference to an object that will live forever
/* MAGIC */ static _Immortalizer<_Ty> _Static;
return (*reinterpret_cast<_Ty *>(&_Static._Storage));
}
inline const error_category& system_category() _NOEXCEPT
{ // get system_category
return (_Immortalize<_System_error_category>());
}
_STD_END
_Immortalize
は規格書の要求を満たすためにstatic storageにクラスを構築するためのものなんだろう。(よく見たら微妙に違うみたいだけど)
_STD_BEGIN
// CLASS _System_error_category
class _System_error_category
: public error_category
{ // categorize an operating system error
public:
_System_error_category() _NOEXCEPT
{ // default constructor
_Addr = _System_addr;
}
virtual const char *name() const _NOEXCEPT
{ // get name of category
return ("system");
}
virtual string message(int _Errcode) const
{ // convert to name of error
const unsigned long _Size = 32767;
string _Narrow(_Size, '\0');
const unsigned long _Val = _Winerror_message(
static_cast<unsigned long>(_Errcode), &_Narrow[0], _Size);
if (_Val == 0)
return ("unknown error");
_Narrow.resize(_Val);
_Narrow.shrink_to_fit();
return (_Narrow);
}
virtual error_condition
default_error_condition(int _Errval) const _NOEXCEPT
{ // make error_condition for error code (generic if possible)
int _Posv = _Winerror_map(_Errval);
if (_Posv != 0)
return (error_condition(_Posv, generic_category()));
else
return (error_condition(_Errval, system_category()));
}
};
_STD_END
ん!?なんかデフォルトコンストラクタがおかしいぞ・・・?
_STD_BEGIN
class _System_error_category
: public error_category
{ // categorize an operating system error
_System_error_category() _NOEXCEPT
{ // default constructor
_Addr = _System_addr;
}
_STD_END
_System_addr
ってなんだ?
・・・そういえばstd::error_category
の実装を載せた時にへんなenumがあったな
_STD_BEGIN
class error_category
{ // categorize an error
protected:
enum : uintptr_t
{ // imaginary addresses for Standard error_category objects
_Future_addr = 1,
_Generic_addr = 3,
_Iostream_addr = 5,
_System_addr = 7
};
};
_STD_END
あった。ご丁寧にコメントが書いてある。
imaginary addresses for Standard error_category objects
Google先生お願いします。
標準error_categoryオブジェクトの架空のアドレス
ほう・・・。つまり、std::error_category
クラスの比較演算子は、このenumによって作られた仮想的なアドレスを比較していると。
・・・いやそれってだめなんじゃ?もう一度規格書をみるよ。
§ 19.5.1.1
19.5.1.1 Class error_category overview [syserr.errcat.overview]
- The class
error_category
serves as a base class for types used to identify the source and encoding of a
particular category of error code. Classes may be derived fromerror_category
to support categories of
errors in addition to those defined in this International Standard. Such classes shall behave as specified in
this subclause. [ Note:error_category
objects are passed by reference, and two such objects are equal
if they have the same address. This means that applications using custom error_category types should
create a single object of each such type. —end note ]
ここでいうaddress
はつまるところポインタのことじゃないのか?
cppjpでの @faithandbrave@github さんの解説も
http://cpprefjp.github.io/reference/system_error/error_category/op_less.html
bool operator<(const error_category& rhs) const noexcept;
error_categoryオブジェクトのポインタの小なり比較を行う。
自身のポインタがrhsオブジェクトへのポインタより小さい場合trueを返し、そうでなければfalseを返す。
と書いている。
結論
VS2015は、それが規格的にいいのかは知らんが、std::error_category
の比較演算子の実装に仮想的なアドレスを使っている。
このせいで自分でstd::error_category
の派生クラスを作るときに困る。
皆さんに聞きたい、この実装ってありなの?
追記:VS2015で仕様が変わった理由らしきもの
https://t.co/MJo8E4d3yg
— おいがみ (@oigami013) 2016年8月29日
ここの <system_error> Fixes の2番目かな
https://t.co/MUzHX3H5zk
ん!?
STL Fixes In VS 2015, Part 2 | Visual C++ Team Blog
https://blogs.msdn.microsoft.com/vcblog/2015/07/14/stl-fixes-in-vs-2015-part-2/
- Error category objects didn’t behave properly across different DLLs/EXEs (DevDiv#666062, DevDiv#1095970/Connect#1053790). The tale of woe here was complicated. Calling generic_category(), for example, is supposed to return a reference to a single unique object, regardless of where it’s called. This is usually achieved by separate compilation into the STL’s DLL (or static LIB). However, we can’t separately compile error_category machinery, because it has a virtual message() returning std::string, whose representation is affected by _ITERATOR_DEBUG_LEVEL. So, generic_category() is implemented header-only – but that means that different user DLLs end up with different instantiations and therefore different objects. (It’s also possible for this to cause trouble between a user’s EXE and the STL’s DLL.) We fixed this to achieve as much conformance as possible. We’ve taught error_category, its derived classes, and its operator==()/operator!=() to consider all generic_category() objects to be equal, even if they live at different addresses in different DLLs (and similarly for the other error category objects in the Standard). This has been implemented so that user-defined error category objects will be unaffected. The only thing we can’t fix is a direct comparison of error_category addresses (code should use operator==() instead).
#[C++] Comparison std::error_code from std::system_error to std::errc enum value incorrect when using DLL-version of run-time library
https://connect.microsoft.com/VisualStudio/feedback/details/1053790
A comparison of the error code from an std::system_error object thrown by std::thread to std::errc::resource_unavailable_try_again appears to produce an incorrect Boolean result when the application uses a DLL-specific version of the run-time library (compiler option "/MD" or "MDd").
The issue can be reproduces by the attached file, "MyTest.cpp", or by the online C++ compiler at http://rextester.com/EMVQXX20044
Christopher Kohlhoff encouraged me to submit this bug report. He suggested that the issue might be caused by the fact that the implementation compares error categories by their address, and from MSVC
うーん、英語が得意じゃない私にはちときつい・・・。
dllにしたところ、アドレスがdll使用側と一致しないぞい、ってこと?(あやふや)