具有没有任何构造函数的标准布局结构的 RVO
RVO with a standard layout struct without any constructors
我有一个表示二进制消息的结构。我想写一个函数从缓冲区(无论是文件还是套接字,都无所谓)中获取下一条这样的记录:
template <typename Record>
Record getNext();
现在,我可以这样写:
template <typename Record>
Record getNext() {
Record r;
populateNext(reinterpret_cast<char*>(&r), // maybe ::read()
sizeof(r)); // or equivalent
return r;
}
很好,让我受益于 RVO。但是,它会调用 Record
的默认构造函数,它可能由具有非平凡默认构造函数的类型组成,这些默认构造函数的工作我想避免——这些不一定是 POD 类型,但它们 是标准布局。
有没有一种方法可以编写 getNext()
,从而避免在 Record
上使用任何构造函数(默认或 copy/move)?理想情况下,当用户调用:
auto record = getNext<Record>();
缓冲区直接读入record
的内存。这可能吗?
no_init
是 no_init_t
.
类型的常量
如果您从 no_init_t
构造一个 pod,您会得到一个未初始化的 pod,并且(假设省略)没有什么可做的。
如果您从 no_init_t
构建非 pod,则必须重写构造函数,并使其不初始化数据。一般class_name(no_init_t):field1(no_init), field2(no_init){}
会做,有时class_name(no_init_t){}
会做(假设所有内容都是pod)。
从 no_init
构建每个成员可以作为成员确实是 pod 的健全性检查。从 no_init
构造的非 pod class 将无法编译,直到您编写 no_init_t
构造函数。
这(必须 no_init
每个成员构造函数)确实会产生一些烦人的 DRY 失败,但我们没有反射,所以你会重复自己并喜欢它。
namespace {
struct no_init_t {
template<class T, class=std::enable_if_t<std::is_pod<T>{}>>
operator T()const{
T tmp;
return tmp;
}
static no_init_t instance() { return {}; }
no_init_t(no_init_t const&) = default;
private:
no_init_t() = default;
};
static const no_init = no_init_t::instance();
}
struct Foo {
char buff[1000];
size_t hash;
Foo():Foo(""){}
template<size_t N, class=std::enable_if_t< (N<=sizeof(buff)) >>
Foo( char const(&in)[N] ) {
// some "expensive" copy and hash
}
Foo(no_init_t) {} // no initialization!
};
struct Record {
int x;
Foo foo;
Record()=default;
Record(no_init_t):
x(no_init), foo(no_init)
{}
};
现在我们可以用no_init
构造Record
,它不会被初始化。
每个 POD class 都没有初始化。每个非 POD class 必须提供一个 no_init_t
构造函数(并且可能尽可能实现非初始化)。
然后你 memcpy
就在上面。
这需要修改您的类型及其包含的类型,以支持非初始化。
是这样的吗?
编辑:
地址对对齐的评论。现在使用匿名联合来确保正确对齐。
TestRecord 现在合并了另一种标准布局类型 egg
添加了证明,即使 egg
具有默认构造函数,class 也不会在被 populateNextRecord()
[ 填充之前构造=30=]
我认为这已经快到了,不是吗?
#include <iostream>
#include <array>
#include <algorithm>
struct egg {
egg(int i) : _val(i) {}
egg() {}
int _val = 6;
friend std::ostream& operator<<(std::ostream& os, const egg& e) {
return os << e._val;
}
};
struct TestRecord {
egg x;
double y;
};
void populateNext(uint8_t* first, size_t length)
{
// do work here
TestRecord data_source { 10, 100.2 };
auto source = reinterpret_cast<uint8_t*>(&data_source);
std::copy(source, source + length, first);
}
template<class Record>
struct RecordProxy
{
RecordProxy() {}
uint8_t* data() {
return _data;
}
static constexpr size_t size() {
return sizeof(Record);
}
Record& as_record() {
return _record;
}
union {
Record _record;
uint8_t _data[sizeof(Record)];
};
};
template <typename Record>
RecordProxy<Record> getNext() {
RecordProxy<Record> r;
populateNext(r.data(), // maybe ::read()
r.size()); // or equivalent
return r;
}
using namespace std;
int main()
{
RecordProxy<TestRecord> prove_not_initialised;
auto& r1 = prove_not_initialised.as_record();
cout << "x = " << r1.x << ", y = " << r1.y << endl;
auto buffer = getNext<TestRecord>();
auto& actual_record = buffer.as_record();
cout << "x = " << actual_record.x << ", y = " << actual_record.y << endl;
return 0;
}
我有一个表示二进制消息的结构。我想写一个函数从缓冲区(无论是文件还是套接字,都无所谓)中获取下一条这样的记录:
template <typename Record>
Record getNext();
现在,我可以这样写:
template <typename Record>
Record getNext() {
Record r;
populateNext(reinterpret_cast<char*>(&r), // maybe ::read()
sizeof(r)); // or equivalent
return r;
}
很好,让我受益于 RVO。但是,它会调用 Record
的默认构造函数,它可能由具有非平凡默认构造函数的类型组成,这些默认构造函数的工作我想避免——这些不一定是 POD 类型,但它们 是标准布局。
有没有一种方法可以编写 getNext()
,从而避免在 Record
上使用任何构造函数(默认或 copy/move)?理想情况下,当用户调用:
auto record = getNext<Record>();
缓冲区直接读入record
的内存。这可能吗?
no_init
是 no_init_t
.
如果您从 no_init_t
构造一个 pod,您会得到一个未初始化的 pod,并且(假设省略)没有什么可做的。
如果您从 no_init_t
构建非 pod,则必须重写构造函数,并使其不初始化数据。一般class_name(no_init_t):field1(no_init), field2(no_init){}
会做,有时class_name(no_init_t){}
会做(假设所有内容都是pod)。
从 no_init
构建每个成员可以作为成员确实是 pod 的健全性检查。从 no_init
构造的非 pod class 将无法编译,直到您编写 no_init_t
构造函数。
这(必须 no_init
每个成员构造函数)确实会产生一些烦人的 DRY 失败,但我们没有反射,所以你会重复自己并喜欢它。
namespace {
struct no_init_t {
template<class T, class=std::enable_if_t<std::is_pod<T>{}>>
operator T()const{
T tmp;
return tmp;
}
static no_init_t instance() { return {}; }
no_init_t(no_init_t const&) = default;
private:
no_init_t() = default;
};
static const no_init = no_init_t::instance();
}
struct Foo {
char buff[1000];
size_t hash;
Foo():Foo(""){}
template<size_t N, class=std::enable_if_t< (N<=sizeof(buff)) >>
Foo( char const(&in)[N] ) {
// some "expensive" copy and hash
}
Foo(no_init_t) {} // no initialization!
};
struct Record {
int x;
Foo foo;
Record()=default;
Record(no_init_t):
x(no_init), foo(no_init)
{}
};
现在我们可以用no_init
构造Record
,它不会被初始化。
每个 POD class 都没有初始化。每个非 POD class 必须提供一个 no_init_t
构造函数(并且可能尽可能实现非初始化)。
然后你 memcpy
就在上面。
这需要修改您的类型及其包含的类型,以支持非初始化。
是这样的吗?
编辑:
地址对对齐的评论。现在使用匿名联合来确保正确对齐。
TestRecord 现在合并了另一种标准布局类型
egg
添加了证明,即使
[ 填充之前构造=30=]egg
具有默认构造函数,class 也不会在被populateNextRecord()
我认为这已经快到了,不是吗?
#include <iostream>
#include <array>
#include <algorithm>
struct egg {
egg(int i) : _val(i) {}
egg() {}
int _val = 6;
friend std::ostream& operator<<(std::ostream& os, const egg& e) {
return os << e._val;
}
};
struct TestRecord {
egg x;
double y;
};
void populateNext(uint8_t* first, size_t length)
{
// do work here
TestRecord data_source { 10, 100.2 };
auto source = reinterpret_cast<uint8_t*>(&data_source);
std::copy(source, source + length, first);
}
template<class Record>
struct RecordProxy
{
RecordProxy() {}
uint8_t* data() {
return _data;
}
static constexpr size_t size() {
return sizeof(Record);
}
Record& as_record() {
return _record;
}
union {
Record _record;
uint8_t _data[sizeof(Record)];
};
};
template <typename Record>
RecordProxy<Record> getNext() {
RecordProxy<Record> r;
populateNext(r.data(), // maybe ::read()
r.size()); // or equivalent
return r;
}
using namespace std;
int main()
{
RecordProxy<TestRecord> prove_not_initialised;
auto& r1 = prove_not_initialised.as_record();
cout << "x = " << r1.x << ", y = " << r1.y << endl;
auto buffer = getNext<TestRecord>();
auto& actual_record = buffer.as_record();
cout << "x = " << actual_record.x << ", y = " << actual_record.y << endl;
return 0;
}