我如何为一个类定义一个 UUID,并以同样的方式使用 __uuidof,对于 g++ 和 Visual C++?

Che*_*Alf 3 c++ winapi uuid g++ visual-c++

注意:这是一个有问必答的问题,目的是记录其他人可能会发现有用的技术,并可能了解其他人的更好解决方案。请随意添加批评或问题作为评论。也可以随意添加其他答案。:)


Visual C++ 一直有一个语言扩展__uuidof(类名),可以检索UUID,一个 128 位通用唯一标识符,前提是 UUID 通过 与类关联__declspec,这也是一个 Visual C++ 语言扩展:

#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
}
Run Code Online (Sandbox Code Playgroud)

MinGW g++ 4.8.2(可能还有一些早期版本)支持__uuidof,但不支持 MSVC 的__declspec. 因此,使用 g++ 4.8.2 编译上述内容会失败,至少在我使用的 Nuwen 发行版中是这样。首先 g++ 发出警告“'uuid'属性指令被忽略”,然后一个链接器错误“未定义引用到_GUID const& __mingw_uuidof<Foo>()”。

该错误提示 UUID 与 g++ 的类相关联的方式,即通过专门化__mingw_uuidof该类的函数模板。

不幸的是,UUID 规范的形式与 Visual C++ 形式完全不同。是数字,不是字符串。它没有隐藏在classorstruct关键字之后,而是遵循类的声明:

#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
}
Run Code Online (Sandbox Code Playgroud)

如何将 UUID 与类相关联,以便它可以与两个编译器一起使用__uuidof,而没有冗余,并且最好将 UUID 作为直接的数字序列(如 Visual C++)?

Che*_*Alf 6

预先免责声明:这些都没有经过广泛的测试或审查。我刚写的。

这一事实提出了一种可能的统一方法:

  • Visual C++__declspec( uuid )不需要提供类的第一个声明:它可以第一个声明之后应用。

例如,Visual C++ 代码可能如下所示:

class Foo
{};

class  __declspec( uuid( "290ff5cb-3a21-4740-bfda-2697ca13deae" ) ) Foo;
Run Code Online (Sandbox Code Playgroud)

因此,哪个编译器是它的嗅探宏CPPX_UUID_FOR可以定义为……

#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
Run Code Online (Sandbox Code Playgroud)

并在类的第一个声明之后调用:

#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;
}
Run Code Online (Sandbox Code Playgroud)

Visual C++ 的宏实现是微不足道的:

#define CPPX_MSVC_UUID_FOR( name, spec )    \
    class __declspec( uuid( spec ) ) name
Run Code Online (Sandbox Code Playgroud)

g++ 的宏实现有点复杂:

#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, "" )
Run Code Online (Sandbox Code Playgroud)

...static_assert唯一的作用是支持调用中的最后一个分号。

字面定义的用户不是绝对必要的,但我认为这是件有趣的事情吧。

cppx::operator"" _uuid如此定义,在命名空间中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
Run Code Online (Sandbox Code Playgroud)

cppx::uuid::from在命名空间中更早地定义了where cppx::uuid

inline CPPX_CONSTEXPR
auto from( char const (&spec)[37] )
    -> Uuid
{ return Initializable( ce, spec ); }
Run Code Online (Sandbox Code Playgroud)

wherece只是一个构造函数标记,枚举类型Const_expr,它选择类的constexpr构造函数uuid::Initializable

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] );
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

两个构造函数的区别主要在于判断代码正确与否的难易程度,但最后一个(我先写的!)也有有用的assert语句。我不确定如何最好地assertconstexpr构造函数做这样的离子。或者甚至这是否可行,这就是为什么有两个构造函数而不是一个构造函数的原因之一。

哦,<<这里的调用只是旧的左移,而不是花哨的自定义运算符符号输出或流或存储操作。:)


的定义nybble_from_hexbyte_from_hex相当琐碎,但fail功能有点微妙。尽管外观,它不是 一个constexpr功能。相反,它是一个不返回的函数。C++11 有一个符号来表达[[noreturn]],但据我所知,Visual C++ 和 g++ 都不支持。因此,我使用编译器特定的注释,如下所示:

#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
Run Code Online (Sandbox Code Playgroud)

然后fail可以简单地编码为例如

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 ); }
Run Code Online (Sandbox Code Playgroud)

我发现当它有参数时将其表达failconstexpr函数是不平凡的(并且可能是不可能的)std::string,并且作为它的普通函数调用抑制了该constexpr属性。非返回变体与 g++ 一起工作正常。但是,我不确定标准对此有何看法。