c – 转发到就地构造函数

我有一个消息类,以前有点痛苦,你必须构造消息类,告诉它为你的对象分配空间,然后通过构造或成员填充空间.

我想使用结果对象的直接内联new构造消息对象成为可能,但是在调用站点使用简单语法同时确保复制省略.

#include <cstdint>

typedef uint8_t id_t;
enum class MessageID { WorldPeace };

class Message
{
    uint8_t* m_data;         // current memory
    uint8_t m_localData[64]; // upto 64 bytes.
    id_t m_messageId;
    size_t m_size; // amount of data used
    size_t m_capacity; // amount of space available
    // ...

public:
    Message(size_t requestSize, id_t messageId)
        : m_data(m_localData)
        , m_messageId(messageId)
        , m_size(0), m_capacity(sizeof(m_localData))
    {
        grow(requestSize);
    }

    void grow(size_t newSize)
    {
        if (newSize > m_capacity)
        {
            m_data = realloc((m_data == m_localData) ? nullptr : m_data, newSize);
            assert(m_data != nullptr); // my system uses less brutal mem mgmt
            m_size = newSize;
        }
    }

    template<typename T>
    T* allocatePtr()
    {
        size_t offset = size;
        grow(offset + sizeof(T));
        return (T*)(m_data + offset);
    }

#ifdef USE_CPP11
    template<typename T, typename Args...>
    Message(id_t messageId, Args&&... args)
        : Message(sizeof(T), messageID)
    {
        // we know m_data points to a large enough buffer
        new ((T*)m_data) T (std::forward<Args>(args)...);
    }
#endif
};

Pre-C 11我有一个讨厌的宏,CONSTRUCT_IN_PLACE,它做了:

#define CONSTRUCT_IN_PLACE(Message, Typename, ...) \
    new ((Message).allocatePtr<Typename>()) Typename (__VA_ARGS__)

你会说:

Message outgoing(sizeof(MyStruct), MessageID::WorldPeace);
CONSTRUCT_IN_PLACE(outgoing, MyStruct, wpArg1, wpArg2, wpArg3);

使用C 11,您可以使用

Message outgoing<MyStruct>(MessageID::WorldPeace, wpArg1, wpArg2, wpArg3);

但我觉得这很混乱.我想要实现的是:

    template<typename T>
    Message(id_t messageId, T&& src)
        : Message(sizeof(T), messageID)
    {
        // we know m_data points to a large enough buffer
        new ((T*)m_data) T (src);
    }

这样用户就可以使用了

Message outgoing(MessageID::WorldPeace, MyStruct(wpArg1, wpArg2, wpArg3));

但似乎这首先在堆栈上构造一个临时的MyStruct,将就地新的转换为对T的移动构造函数的调用.

其中许多消息很简单,通常是POD,它们经常处于这样的编组功能:

void dispatchWorldPeace(int wpArg1, int wpArg2, int wpArg3)
{
    Message outgoing(MessageID::WorldPeace, MyStruct(wpArg1, wpArg2, wpArg3));
    outgoing.send(g_listener);
}

所以我想避免创建一个需要后续移动/复制的中间临时.

似乎编译器应该能够消除临时和移动并将构造一直向前转移到就地新的.

我在做什么导致它不? (GCC 4.8.1,Clang 3.5,MSVC 2013)

最佳答案 您将无法在放置新位置中删除复制/移动:复制省略完全基于编译器在构造时知道对象最终将最终结束的想法.此外,由于复制省略实际上改变了程序的行为(毕竟,它不会调用相应的构造函数和析构函数,即使它们有副作用)复制省略仅限于几个非常特殊的情况(列于12.8 [ class.copy]第31段:主要是当按名称返回局部变量时,按名称抛出局部变量,按值捕获正确类型的异常,以及复制/移动临时变量时;请参阅子句以获取确切的详细信息).由于[placement] new不是可以省略副本的上下文,并且构造函数的参数显然不是临时的(它被命名),因此永远不会省略复制/移动.即使将缺少的std :: forward< T>(…)添加到构造函数中,也会导致复制/移动被省略:

template<typename T>
Message(id_t messageId, T&& src)
    : Message(sizeof(T), messageID)
{
    // placement new take a void* anyway, i.e., no need to cast
    new (m_data) T (std::forward<T>(src));
}

我不认为您在调用构造函数时可以显式指定模板参数.因此,我认为如果不提前构建对象并将其复制/移动,您可能得到的最接近的是这样的:

template <typename>
struct Tag {};

template <typename T, typename A>
Message::Message(Tag<T>, id_t messageId, A... args)
    : Message(messageId, sizeof(T)) {
    new(this->m_data) T(std::forward<A>(args)...);
}

一种可能使事情变得更好的方法是使用id_t映射到相关类型,假设存在从消息ID到相关类型的映射:

typedef uint8_t id_t;
template <typename T, id_t id> struct Tag {};
struct MessageId {
    static constexpr Tag<MyStruct, 1> WorldPeace;
    // ...
};
template <typename T, id_t id, typename... A>
Message::Message(Tag<T, id>, A&&... args)
    Message(id, sizeof(T)) {
    new(this->m_data) T(std::forward<A>)(args)...);
}
点赞