2

通过阅读这篇文章,很明显 C++ 中的放置消息用于在预分配的内存位置调用类构造函数。

在内存已经初始化的情况下,placement new 还是 reinterpret_cast 更合适?

例如,假设我从 TCP 套接字读取表示帧消息的原始字节流。我将此流放入帧同步并检索一个已知大小的缓冲区,该缓冲区代表我的类,我将其称为 Message。我知道有两种方法可以继续。

  1. 创建一个带有标志的构造函数,告诉类不要初始化。在传递“不初始化”标志的缓冲区上做一个新的放置。

    Message::Message( bool initialize ) 
    {
        //
        // Initialize if requested
        //
        if( initialize )
        {
            Reset( );
        }
    }
    
    void Message::Reset( void )
    {
       m_member1 = 1;
       m_member2 = 2;
    }
    
    Message* message = new ( buffer ) Message( false );
    
  2. 使用 reinterpret_cast

    Message* message = reinterpret_cast< Message* > ( buffer ); 
    

我相信这两者都会产生相同的结果。一个比另一个更正确、更面向对象、更安全、更易于阅读还是更好的风格?

4

2 回答 2

11

唯一有意义的规则是:

T如果已经在 address 构造了某种类型的实例,则获取指向已经存在的对象指针areinterpret_cast<T*>(a)

如果尚未在 address 构造某种类型的实例T,则使用aplacement newT在 addres构造类型的实例a

它们是完全不同的操作。

你需要问的问题非常非常简单:“对象是否已经存在?” 如果是,您可以访问它(通过演员表)。如果不是,那么您需要构建它(通过放置新)

这两个操作彼此无关。

这不是你应该更喜欢哪一个的问题,因为他们做不同的事情。你应该更喜欢那个做你想做的事。

于 2012-07-24T16:45:28.503 回答
1

我也不会说。

使用placement new 并具有特殊的构造方法似乎是一种技巧。一方面,标准说,例如,未初始化的 int 类成员具有“不确定值”,并且访问它“可能”导致未定义的行为。没有指定 int 将假定未修改的底层字节的值解释为 int。我不认为有任何东西可以阻止一致的实现在调用构造函数之前将内存初始化为零。

为了很好地定义 reinterpret_cast 的这种使用,您必须跳过一些障碍,即使这样使用生成的对象也可能会违反严格的别名规则。

更实际地,如果您直接通过网络发送一个类的实现指定的表示,您将依赖于具有兼容布局(兼容表示、对齐等)的通信系统。

相反,您应该进行真正的序列化和反序列化,例如使用 memcpy() 和 ntoh() 将数据从缓冲区获取到现有对象的成员中。

struct Message {
    uint32_t m_member1;
    uint16_t m_member2;
};

extern char *buffer;

Message m;

memcpy(&m.m_member1, buffer, sizeof m.m_member1);
m.m_member1 = ntohl(m.m_member1);
buffer += sizeof m.m_member1;

memcpy(&m.m_member2, buffer, sizeof m.m_member2);
m.m_member2 = ntohs(m.m_member2);
buffer += sizeof m.m_member2;

如果您不只是使用预先存在的库,您可能希望将这些东西包装在您自己的框架中。

这样您就不必处理对齐问题,网络表示定义良好,可以在不同的实现之间传递,并且程序不使用技术上未定义的行为。

于 2012-07-24T18:38:15.477 回答