RVO со стандартной структурой компоновки без каких-либо конструкторов
У меня есть структура, представляющая двоичное сообщение. Я хочу написать функцию, чтобы получить следующую такую запись из буфера (будь то файл или сокет, не имеет значения):
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
? В идеале, когда пользователь вызывает:
auto record = getNext<Record>();
Буфер считывается непосредственно в память Record
. Это возможно?
Ответы
Ответ 1
no_init
является константой типа no_init_t
.
Если вы построите pod из no_init_t
, вы получите неинициализированный модуль, и (при условии, что это элиция) ничего не поделаешь.
Если вы создаете не-pod из no_init_t
, вы должны переопределить конструктор и не инициализировать данные. Обычно class_name(no_init_t):field1(no_init), field2(no_init){}
будет делать это, а иногда class_name(no_init_t){}
сделает это (при условии, что все содержимое pod).
Построение из no_init
для каждого члена может действовать как проверка на здравомыслие, что члены действительно являются модулями. Класс non-pod, построенный из no_init
, не скомпилируется до тех пор, пока вы не напишите конструктор 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)
{}
};
Теперь мы можем построить Record
с no_init
, и он не будет инициализирован.
Каждый класс POD не инициализируется. Каждый класс, отличный от POD, должен предоставить конструктор no_init_t
(и, по-видимому, реализовать неинициализацию, насколько это возможно).
Затем вы memcpy
прямо над ним.
Это требует модификации вашего типа и типов, которые он содержит, для поддержки неинициализации.
Ответ 2
Что-то вроде этого?
ИЗМЕНИТЬ:
-
Прокомментирует выравнивание. Теперь используется анонимный союз для обеспечения правильного выравнивания.
-
TestRecord теперь включает другой стандартный тип макета egg
-
Добавлено доказательство того, что даже если egg
имеет конструктор по умолчанию, класс не строится до заполнения 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;
}