我有一个消息类,以前使用起来有点麻烦,您必须构造消息类,告诉它为您的对象分配空间,然后通过构造或成员方式填充空间。
我希望能够使用结果对象的直接内联 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
};
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
,将就地 new
转换为对 T 的移动构造函数的调用。
其中许多消息都很简单,通常是 POD,并且它们通常在这样的编码函数中:
void dispatchWorldPeace(int wpArg1, int wpArg2, int wpArg3)
{
Message outgoing(MessageID::WorldPeace, MyStruct(wpArg1, wpArg2, wpArg3));
outgoing.send(g_listener);
}
所以我想避免创建一个需要后续移动/复制的中间临时文件。
看起来编译器应该能够消除临时的,并且将构造一直向下移动到就地 new
。
我在做什么导致它不这样做? (GCC 4.8.1、Clang 3.5、MSVC 2013)
最佳答案
您将无法在新位置省略复制/移动:复制省略完全基于编译器在构建时知道对象最终将在哪里结束的想法。此外,由于复制省略实际上改变了程序的行为(毕竟,它不会调用相应的构造函数和析构函数,即使它们有副作用)复制省略仅限于一些非常特殊的情况(列在 12.8 [ class.copy] 第 31 段:本质上,当按名称返回局部变量时,当按名称抛出局部变量时,当按值捕获正确类型的异常时,以及当复制/移动临时变量时;请参阅条款以获取确切的详细信息).自 [放置] 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)...);
}
关于c++ - 转发到就地构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21492264/