平凡的可复制总是伪标准布局?

时间:2012-03-17 17:17:40

标签: c++ c++11

是否有任何编译器的标准布局类型的布局要求也不适用于平凡的可复制类型?特别是,关键规则是指向该类型的指针是指向其第一个成员的指针(其中基类将被视为在派生类之前)。也就是说,该类型的地址与其基本类型的地址相同。

在代码中,是否有任何常见的编译器,其中以下实际上不起作用。对我来说这似乎是常见的做法,因此我很惊讶它在C ++ 11中没有标准化。

struct base { int a; /* is a trivial class*/ };
struct derived : public base { int b; /*still a trivial class*/ }

void copy( base * a, base * b, size_t len )
{
   memcpy( a, b, len );
}

...
derived d1, d2;
copy( &d1, &d2, sizeof(derived) );

我确信这在GCC中有效,我相信它在MSVC中有效(尽管我可能错了)。在哪些非历史编译器中上述不能按预期工作?


扩展示例

上面的例子显示了根本问题,但可能没有显示出在那里的意图。这是一个稍微冗长的例子。基本上任何人都可以调用“send”来排队消息,然后通过回送到真实类型来调度每条消息。

struct header { int len, id; }
struct derived : public header { int other, fields; }

void send( header * msg )
{ 
   char * buffer = get_suitably_aligned_buffer( msg->len );
   memcpy( buffer, msg, msg->len ); 
}

void dispatch( char * buffer )
{
  header * msg = static_cast<header*>(buffer);
  if( msg->id == derived_id )
    handle_derived( static_cast<derived*>(msg) );
}


derived d;
d.len = sizeof(d);
d.id = deirved_id;
send( &d );

...
char * buffer = get_the_buffer_again();
dispatch( buffer );

它仍然忽略了许多方面,但显示了关键部分。

2 个答案:

答案 0 :(得分:1)

  

我确信这在GCC中有效,我相信它在MSVC中有效(尽管我可能错了)。

不,不。您已经在这些编译器上运行了一些不破坏的示例。这与知道“肯定”任何东西都不同。

未定义未定义的行为。下一版GCC可能会破坏您的代码。 Visual Studio的下一个版本可能会破坏您的代码。实际上,在发布或某些优化中进行编译可能会破坏您的代码。

遵循标准是唯一可以“确切知道”任何事情的方法。做你正在做的事情不是实现定义的行为;它是未定义的行为。因此,即使 出现,你也不能相信你会得到一致的答案。

答案 1 :(得分:1)

是的,只要单继承存在,人们就已经在C ++中这样做了。是的,这基本上是合理的。不,标准不支持它。是普遍支持吗?可能,但你似乎已经知道这不重要。这类问题是标准化应该消除的。

无论好坏,C ++确实为这个问题提供了一个解决方案,尽管这个问题明显不那么优雅。

问题在于派生类中的非静态数据成员不一定在基础成员之后遵循相同的填充,就好像它们被直接拼接到基础中一样。

union标准布局结构与一个共同的初始序列(故意避免继承)确实得到了这种保证。

struct header { int len, id; }

union derived {
    struct {
        header h;
        int payload;
    } fmt1;

    struct {
        header h; // repetitive
        double payload;
    } fmt2;

    // etc for all message types
};

当多个空基类被包含时,布局实际上可能不同,特别是如果第一个非静态数据成员与空基类的类型相同。继承(仍然)不能做到这一点的原因可能是他们厌倦了写关于空基的特殊情况。