I have a struct representing a binary message. I want to write a function to get the next such record from a buffer (whether a file or a socket, doesn't matter):
template <typename Record>
Record getNext();
Now, I could write this like:
template <typename Record>
Record getNext() {
Record r;
populateNext(reinterpret_cast<char*>(&r), // maybe ::read()
sizeof(r)); // or equivalent
return r;
}
which is nice and gives me the benefits of RVO. However, it will invoke the default constructor of Record
, which may be composed of types with non-trival default constructors which do work that I would like to avoid - these are not necessarily POD types, but they are standard layout.
Is there a way to write getNext()
such that we avoid any constructors (default or copy/move) on Record
? Ideally, when the user calls:
auto record = getNext<Record>();
The buffer is read directly into the memory of record
. Is this possible?
Technically, a struct is like a class , so technically a struct would naturally benefit from having constructors and methods, like a class does.
C# does not allow a struct to declare a default, no-parameters, constructor. The reason for this constraint is to do with the fact that, unlike in C++, a C# struct is associated with value-type semantic and a value-type is not required to have a constructor.
struct can include constructors, constants, fields, methods, properties, indexers, operators, events & nested types. struct cannot include a parameterless constructor or a destructor. struct can implement interfaces, same as class. struct cannot inherit another structure or class, and it cannot be the base of a class.
// In C++ We can Initialize the Variables with Declaration in Structure. Structure members can be initialized using curly braces '{}'.
no_init
is a constant of type no_init_t
.
If you construct a pod from a no_init_t
, you get an uninitialized pod, and (assuming elision) there is nothing to be done.
If you construct a non-pod from a no_init_t
, you have to override a constructor, and make it not initialize the data. Usually class_name(no_init_t):field1(no_init), field2(no_init){}
will do it, and sometimes class_name(no_init_t){}
will do it (assuming all contents are pod).
Constructing from no_init
on each member can act as a sanity check that the members are indeed pod, however. A non-pod class constructed from no_init
will fail to compile until you write the no_init_t
constructor.
This (having to no_init
each member constructor) does generate some annoying DRY failure, but we don't got reflection, so you are gonna repeat yourself and like it.
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)
{}
};
Now we can construct Record
with no_init
and it won't be initialized.
Every POD class is not initialized. Every non-POD class must provide a no_init_t
constructor (and presumably implement non-initialization, as best it can).
You then memcpy
right over it.
This requires modifying your type, and the types it contains, to support non-initialization.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With