如何以与 g++ 和 Visual C++ 相同的方式为类定义 UUID 并使用 __uuidof?
注意:这是一个question-with-answer,以便记录其他人可能认为有用的技术,以便了解其他人"更好的解决方案.请随意添加批评或问题作为评论.也请随意添加其他答案.:)
Visual C++ 一直有一个语言扩展 __uuidof(
classname)
可以检索 UUID,一个 128 位的通用唯一标识符,前提是 UUID 已通过 __declspec
与类关联,这也是 Visual C++ 语言扩展:
Visual C++ has always had a language extension __uuidof(
classname)
that can retrieve an UUID, a 128 bit Universally Unique Identifier, provided that the UUID’s been associated with the class via __declspec
, which also is a Visual C++ language extension:
#include <guiddef.h> // GUID, another name for UUID
class
__declspec( uuid( "290ff5cb-3a21-4740-bfda-2697ca13deae" ) )
Foo
{};
#include <iostream>
using namespace std;
auto main()
-> int
{
cout << hex << __uuidof( Foo ).Data1 << endl; // 290ff5cb
}
MinGW g++ 4.8.2(可能还有一些更早的版本)支持 __uuidof
,但不支持 MSVC 的 __declspec
.因此,使用 g++ 4.8.2 编译上述内容会失败,至少在我使用的 Nuwen 发行版中是这样.首先 g++ 发出警告 “'uuid' 属性指令被忽略”,然后得到一个链接器错误 “undefined reference to _GUID const&__mingw_uuidof<Foo>()
”.
MinGW g++ 4.8.2 (and possibly some earlier versions) supports __uuidof
, but it does not support MSVC’s __declspec
. Compiling the above with g++ 4.8.2 therefore fails, at least with the Nuwen distribution that I use. First g++ issues the warning “'uuid' attribute directive ignored”, and then one gets a linker error “undefined reference to _GUID const& __mingw_uuidof<Foo>()
”.
该错误提示 UUID 与 g++ 类关联的方式,即通过为该类专门化函数模板 __mingw_uuidof
.
The error hints at the way an UUID is associated with a class for g++, namely by specializing the function template __mingw_uuidof
for the class.
不幸的是,UUID 规范的形式与 Visual C++ 形式完全不同.它是数字,而不是字符串.而且它不是藏在 class
或 struct
关键字之后,而是在类的声明之后:
Unfortunately the UUID specification is then of a form that’s radically different from the Visual C++ form. It’s numbers, not a string. And it’s not tucked in after the class
or struct
keyword, but follows a declaration of the class:
#include <guiddef.h> // GUID, another name for UUID
class Foo {};
template<>
auto __mingw_uuidof<Foo>()
-> GUID const&
{
static const GUID the_uuid =
{
0x290ff5cb, 0x3a21, 0x4740,
{ 0xbf, 0xda, 0x26, 0x97, 0xca, 0x13, 0xde, 0xae }
};
return the_uuid;
}
#include <iostream>
using namespace std;
auto main()
-> int
{
cout << hex << __uuidof( Foo ).Data1 << endl; // 290ff5cb
}
如何将 UUID 与一个类相关联,以便它可以与两种编译器一起使用?__uuidof
,没有冗余,最好使用 UUID 作为直接的数字序列(与 Visual C++ 一样)?
How can one associate an UUID with a class so that it will work with both compilers’ __uuidof
, without redundancy, and preferably with the UUID as a direct sequence of digits (as with Visual C++)?
推荐答案
预先免责声明:这一切都没有经过广泛的测试或审查.我刚写的.
这一事实提出了一种可能的统一方法:
One possible unification approach is suggested by this fact:
- Visual C++
__declspec(uuid)
不需要与类的第一个声明一起提供:它可以在第一个声明之后应用.李>
- A Visual C++
__declspec( uuid )
doesn’t need to be provided with the first declaration of a class: it can be applied after the first declaration.
例如Visual C++ 代码可能如下所示:
E.g. Visual C++ code can look like this:
class Foo
{};
class __declspec( uuid( "290ff5cb-3a21-4740-bfda-2697ca13deae" ) ) Foo;
因此,which-compiler-is-it 嗅探宏 CPPX_UUID_FOR
可以定义为 …
Thus a which-compiler-is-it sniffing macro CPPX_UUID_FOR
can be defined as …
#if !defined( CPPX_UUID_FOR )
# if defined( _MSC_VER )
# define CPPX_UUID_FOR CPPX_MSVC_UUID_FOR
# elif defined( __GNUC__ )
# define CPPX_UUID_FOR CPPX_GNUC_UUID_FOR
# endif
#endif
并在类的第一个声明之后被调用:
and be invoked after the first declaration of the class:
#include <iostream>
using namespace std;
struct Foo {};
CPPX_UUID_FOR( Foo, "dbe41a75-d5da-402a-aff7-cd347877ec00" );
void test()
{
using cppx::uuid::operator<<;
cout << setw( 20 ) << "__uuidof: " << __uuidof( Foo ) << endl;
}
Visual C++ 的宏实现很简单:
The macro implementation for Visual C++ is trivial:
#define CPPX_MSVC_UUID_FOR( name, spec )
class __declspec( uuid( spec ) ) name
g++ 的宏实现有点复杂:
The macro implementation for g++ is a bit more involved:
#define CPPX_GNUC_UUID_FOR( name, spec )
template<>
inline
auto __mingw_uuidof<name>()
-> GUID const&
{
using cppx::operator"" _uuid;
static constexpr GUID the_uuid = spec ## _uuid;
return the_uuid;
}
template<>
inline
auto __mingw_uuidof<name*>()
-> GUID const&
{ return __mingw_uuidof<name>(); }
static_assert( true, "" )
…其中 static_assert
仅用于支持调用中的最后一个分号.
… where the static_assert
only serves to support a final semicolon in the invocation.
用户定义的文字并不是绝对必要的,但我认为这样做很有趣.
The user defined literal is not strictly necessary, but I thought it was interesting to do it.
cppx::operator"" _uuid
是这样定义的,在命名空间 cppx
:
cppx::operator"" _uuid
is defined thusly, in namespace cppx
:
namespace detail {
CPPX_CONSTEXPR
auto uuid_from_spec( char const* const s, size_t const size )
-> cppx::Uuid
{
return (
size == 36? cppx::uuid::from(
reinterpret_cast<char const (&)[37]>( *s )
) :
cppx::fail(
"An uuid spec must be 36 chars, like"
" "dbe41a75-d5da-402a-aff7-cd347877ec00""
)
);
}
} // namespace detail
#if !(defined( _MSC_VER ) || defined( NO_USER_LITERALS ))
CPPX_CONSTEXPR
auto operator"" _uuid( char const* const s, size_t const size )
-> cppx::Uuid
{ return detail::uuid_from_spec( s, size ); }
#endif
其中 cppx::uuid::from
之前在命名空间 cppx::uuid
:
where cppx::uuid::from
is defined earlier in namespace cppx::uuid
:
inline CPPX_CONSTEXPR
auto from( char const (&spec)[37] )
-> Uuid
{ return Initializable( ce, spec ); }
其中ce
只是一个构造函数标记,枚举类型Const_expr
,它选择constexpr
构造函数uuid::Initializable
类:
where ce
is just a constructor tag, of enumeration type Const_expr
, that selects the constexpr
constructor of the uuid::Initializable
class:
struct Initializable: Uuid
{
explicit CPPX_CONSTEXPR
Initializable( Const_expr, char const (&spec)[37] )
: Uuid( {
// Data1
(((((((((((((
static_cast<unsigned long>( nybble_from_hex( spec[0] ) )
<< 4) | nybble_from_hex( spec[1] ))
<< 4) | nybble_from_hex( spec[2] ))
<< 4) | nybble_from_hex( spec[3] ))
<< 4) | nybble_from_hex( spec[4] ))
<< 4) | nybble_from_hex( spec[5] ))
<< 4) | nybble_from_hex( spec[6] ))
<< 4) | nybble_from_hex( spec[7] ),
// Data2
static_cast<unsigned short>(
(((((
static_cast<unsigned>( nybble_from_hex( spec[9] ) )
<< 4) | nybble_from_hex( spec[10] ))
<< 4) | nybble_from_hex( spec[11] ))
<< 4) | nybble_from_hex( spec[12] )
),
// Data 3
static_cast<unsigned short>(
(((((
static_cast<unsigned>( nybble_from_hex( spec[14] ) )
<< 4) | nybble_from_hex( spec[15] ))
<< 4) | nybble_from_hex( spec[16] ))
<< 4) | nybble_from_hex( spec[17] )
),
// Data 4
{
static_cast<unsigned char>( byte_from_hex( spec[19], spec[20] ) ),
static_cast<unsigned char>( byte_from_hex( spec[21], spec[22] ) ),
static_cast<unsigned char>( byte_from_hex( spec[24], spec[25] ) ),
static_cast<unsigned char>( byte_from_hex( spec[26], spec[27] ) ),
static_cast<unsigned char>( byte_from_hex( spec[28], spec[29] ) ),
static_cast<unsigned char>( byte_from_hex( spec[30], spec[31] ) ),
static_cast<unsigned char>( byte_from_hex( spec[32], spec[33] ) ),
static_cast<unsigned char>( byte_from_hex( spec[34], spec[35] ) )
}
} )
{}
explicit
Initializable( char const (&spec)[37] )
: Uuid()
{
for( int i = 0; i < 8; ++i )
{
Uuid::Data1 = (Uuid::Data1 << 4) | nybble_from_hex( spec[i] );
}
assert( spec[8] == '-' );
for( int i = 9; i < 13; ++i )
{
Uuid::Data2 = (Uuid::Data2 << 4) | nybble_from_hex( spec[i] );
}
assert( spec[13] == '-' );
for( int i = 14; i < 18; ++i )
{
Uuid::Data3 = (Uuid::Data3 << 4) | nybble_from_hex( spec[i] );
}
assert( spec[18] == '-' );
for( int i = 19; i < 23; i += 2 )
{
Uuid::Data4[(i - 19)/2] = byte_from_hex( spec[i], spec[i + 1] );
}
assert( spec[23] == '-' );
for( int i = 24; i < 36; i += 2 )
{
Uuid::Data4[2 + (i - 24)/2] = byte_from_hex( spec[i], spec[i + 1] );
}
}
};
这两个构造函数的主要区别在于判断代码正确与否的难易程度,但最后一个(我第一个写的!)也有有用的 assert
语句.我不确定如何最好地为 constexpr
构造函数执行此类 assert
ions.甚至这是否可行,这也是为什么有两个构造函数而不是只有一个的原因之一.
The two constructors mainly differ in how easy it is to judge the correctness or not of the code, but the last one (I wrote that first!) also has useful assert
statements. I'm not sure how to best do such assert
ions for the constexpr
constructor. Or even whether that is doable, which is one reason why there are two constructors instead of just one.
哦,这里的 <<
调用只是很好的旧左移,而不是花哨的自定义运算符符号输出或流或存储操作.:)
Oh, the <<
invocations here are just good old left-shifts, not fancy custom operator-notation output or stream or store operations. :)
nybble_from_hex
和 byte_from_hex
的定义很简单,但是 fail
函数有点微妙.尽管看起来它 not 是一个 constexpr
函数.相反,它是一个不返回函数.C++11 有一个表示法,[[noreturn]]
,但据我所知,Visual C++ 和 g++ 都不支持它.因此,我使用编译器特定的注释,如下所示:
The definitions of nybble_from_hex
and byte_from_hex
are pretty trivial, but the fail
function is a bit subtle. In spite of appearances it’s not a constexpr
function. Instead, it’s a non-returning function. C++11 has a notation to express that, [[noreturn]]
, but as far as I know neither Visual C++ nor g++ supports that yet. So instead I use compiler specific annotations, like this:
#if !defined( CPPX_NORETURN )
# if defined( _MSC_VER )
# define CPPX_NORETURN __declspec( noreturn )
# elif defined( __GNUC__ )
# define CPPX_NORETURN __attribute__((noreturn))
# else
# define CPPX_NORETURN [[noreturn]]
# endif
#endif
然后 fail
可以简单地编码为例如
and then fail
can be coded up simply as e.g.
struct Whatever
{
template< class Type >
CPPX_CONSTEXPR operator Type () const { return Type(); }
};
inline
CPPX_NORETURN
auto fail( string const& s )
-> Whatever
{ throw runtime_error( s ); }
我发现当 std::string
参数具有 std::string
参数时,将 fail
表示为 constexpr
函数并非易事(而且可能是不可能的),并且作为普通函数调用它抑制了 constexpr
属性.非返回变体适用于 g++.但是,我不确定标准对此有何规定.
I found it non-trivial (and possibly impossible) to express fail
as a constexpr
function when it has std::string
argument, and as an ordinary function calls of it suppressed the constexpr
property. The non-returning variant works OK with g++. However, I’m not sure what the standard has to say about this.
相关文章