So. Nachdem der GCC-Bug endlich behoben wurde, kann man auch mit GCC aus dem SVN den Code kompilieren.
Das bedeutet, dieser Code kompiliert mit GCC >= 4.8 und Clang >= 3.1.
MSVC unterstützt zurzeit nicht genug von C++.
Nochmals:
Der Zweck dieses Templates ist es, eine stark vereinfachtes "struct" zu deklarieren, das allerdings garantiert keinem Padding unterliegt - das bedeutet alle Datenmember liegen hintereinander (in Deklarationsreihenfolge) und ohne Zusatzbytes im Speicher.
Dies kann beispielsweise nützlich sein, um ein Strukturiertes Objekt direkt über das Netzwerk zu senden und auf einem anderen System zu empfangen, ohne auf mögliches Padding Rücksicht nehmen zu müssen.
Die "Deklaration" läuft über ein typedef, wobei vorher die Namen der Datenmember als leere structs deklariert werden müssen:
Code: Alles auswählen
#include "packedstruct.hpp"
struct age {}; // "Name" für den ersten Member
struct weight {}; // "Name" für den zweiten Member
struct cupsize {};
typedef PackedStruct<
unsigned short, age,
double, weight,
char, cupsize
> PackedTrivial;
Anschließend kann ein Objekt konstruiert werden, entweder mit oder ohne Argumente für den Konstruktor.
Der Zugriff erfolgt über die get()-Methode, mit den zuvor deklarierten "tag"-Typen als templateargument:
Code: Alles auswählen
PackedTrivial trivial_init{21, 57.4, 'B'};
PackedTrivial trivial_uninit;
trivial_uninit.get<weight>() = 61.3;
assert(trivial_init.get<weight>() == 57.4);
Wird das Objekt zerstört, werden die Destruktoren der einzelnen Member aufgerufen.
Auch die const-Correctness sollte passen, d.H. auf einen const-Member wird nur eine const-Referenz zurückgegeben.
Dabei gilt IMMER die Garantie, dass das sizeof() des Typs genau der Summer der sizeof()'s der Membertypen entspricht.
Einschränkungen:
- Hat ein Member einen Konstruktor, der mehrere Argumente braucht, muss man bei der Initialisierung ein temporäres Objekt übergeben:
Code: Alles auswählen
class MyType; // hat einen Konstruktor MyType(int, double)
struct size {};
struct myval {};
typedef nuke_ms::PackedStruct
<
std::size_t, size,
MyType, myval
> PackedNonTrivial;
PackedNonTrivial nontrivial{33, MyType{12, 99.999}}; // <-- MyType muss angegeben werden
- Referenztypen werden nicht unterstützt. Diese kann man in std::reference_wrapper<> packen. (Ich glaube diesen Fall müsste man auch einbauen können, hatte aber noch keinen Bedarf dafür. Wenn das jemand machen will, nur zu.)
Hier ist der Code:
packedstruct.hpp
Code: Alles auswählen
#ifndef PACKEDSTUCT_HPP_INCLUDED
#define PACKEDSTUCT_HPP_INCLUDED
#ifndef PACKEDSTRUCT_POD_ONLY
# define PACKEDSTRUCT_NONTRIVIAL
#endif
namespace packedstruct_detail {
template <
std::size_t Offset,
typename VarType, typename NameTag,
typename... Rest
>
struct PackedStructMember
{
static constexpr std::size_t offset = Offset;
typedef VarType vartype;
typedef NameTag nametag;
typedef PackedStructMember<offset + sizeof(vartype), Rest...> next;
static constexpr std::size_t list_size = sizeof(vartype) + next::list_size;
static const vartype& access(const char* base) // const
{
return *reinterpret_cast<const vartype*>(base + offset);
}
static vartype& access(char* base)
{
return *reinterpret_cast<vartype*>(base + offset);
}
static bool all_equal(const char* base, const char* other_base)
{
return (access(base) == access(other_base))
&& next::all_equal(base, other_base);
}
#ifdef PACKEDSTRUCT_NONTRIVIAL
static void construct(char* member_ptr)
{
// use placement new to initialize
new (member_ptr) vartype{};
// defer to next element initialization
next::construct(member_ptr + sizeof(vartype));
}
template <typename ArgumentType, typename... ArgTypeRest>
static void construct(
char* member_ptr,
ArgumentType&& arg,
ArgTypeRest... args_rest
)
{
// use placement new to initialize, forward arguments
new (member_ptr) vartype(std::forward<ArgumentType>(arg));
next::construct(
member_ptr + sizeof(vartype),
std::forward<ArgTypeRest>(args_rest)...
);
}
static void destruct(char* member_ptr)
{
// call destructor explicitly
reinterpret_cast<vartype*>(member_ptr)->~vartype();
// call next node with pointer after the current element
next::destruct(member_ptr + sizeof(vartype));
}
#endif
};
template <std::size_t Offset, typename VarType, typename NameTag>
struct PackedStructMember<Offset, VarType, NameTag>
{
static constexpr std::size_t offset = Offset;
typedef VarType vartype;
typedef NameTag nametag;
// we are at the last member, the size of the list is just the size of our
// variable type
static constexpr std::size_t list_size = sizeof(vartype);
static const vartype& access(const char* base) // const
{
return *reinterpret_cast<const vartype*>(base + offset);
}
static vartype& access(char* base)
{
return *reinterpret_cast<vartype*>(base + offset);
}
static bool all_equal(const char* base, const char* other_base)
{ return (access(base) == access(other_base)); }
#ifdef PACKEDSTRUCT_NONTRIVIAL
static void construct(char* member_ptr)
{
// use placement new to initialize
new (member_ptr) vartype{};
}
template <typename ArgumentType>
static void construct(char* member_ptr, ArgumentType&& arg)
{
// use placement new to initialize, forward arguments
new (member_ptr) vartype(std::forward<ArgumentType>(arg));
}
static void destruct(char* member_ptr)
{
// call destructor explicitly
reinterpret_cast<vartype*>(member_ptr)->~vartype();
}
#endif
};
// Search for a member by name tag.
template <typename MemberList, typename NameTag>
struct FindMemberByNameTag
{
// this means we don't have it, ask the next one
typedef typename FindMemberByNameTag<typename MemberList::next, NameTag>::member
member;
};
// specialization, if the searched name tag is our name tag
template <typename MemberList>
struct FindMemberByNameTag<MemberList, typename MemberList::nametag>
{
typedef MemberList member;
};
/** Packed struct.
* This class represents a simplified struct type that provides
* construction/destruction and access by reference, similar to a normal
* C++-struct.
* However, this class is guaranteed to be packed, that means it has NO padding.
* All members are stored consecutively in declaration order, without gaps.
*
* To "declare" a struct, one has to first declare "tag"-types. These types
* can and should be empty structs, because only their names will be used and no
* objects will be created.
* A member is declared in the struct by passing a pair of member type and "tag"
* type as template argument. It is recommended to typedef the struct for ease
* of use.
*
* @code
struct age {};
struct weight {};
struct cupsize {};
typedef PackedStruct<
unsigned short, age,
double, weight,
char, cupsize
> MyType;
* @endcode
*
* Upon construction of an object of this type, the members are initialized
* either by a default constructor or, when passing arguments to the
* constructor, with the corresponding arguments.
*
* Upon destruction, the destructor of each member is called.
*
* To access a member, use the get() method and pass the "tag" type as template
* argument.
*
* @tparam Members A list of member type/"tag"-type pairs. Each tag type will
* be used to access one particular member of the corresponding type.
*/
template <typename... Members>
class PackedStruct {
static_assert(sizeof...(Members) % 2 == 0,
"Members must be added in pairs of Variable-Type / Name-Tag"
);
typedef PackedStructMember<0, Members...> MemberList;
char data[MemberList::list_size];
public:
#ifdef PACKEDSTRUCT_NONTRIVIAL
/** Default constructor.
* Default-constructs all members
*/
PackedStruct()
{
MemberList::construct(data);
}
/** Constructor.
* Construct all members with an initializer.
* The number of arguments must match the number of struct members.
*
* @param args Arguments to pass to the constructors of the members.
*/
template <typename... ArgTypes>
explicit PackedStruct(ArgTypes&&... args)
{
static_assert(
sizeof...(ArgTypes) == sizeof...(Members) / 2,
"Number of constructor arguments does not match number of struct members."
);
MemberList::construct(data, std::forward<ArgTypes>(args)...);
}
/** Destructor.
* Calls destructor of each data member
*/
~PackedStruct()
{
MemberList::destruct(data);
}
#endif
/** Access data member. */
template <typename NameTag>
typename FindMemberByNameTag<MemberList, NameTag>::member::vartype& get()
{
return FindMemberByNameTag<MemberList, NameTag>::member::access(data);
}
/** Access data member.
* @tparam NameTag "tag" type that was used as a name for a member in the
* template arguments of this class.
*/
template <typename NameTag>
const typename FindMemberByNameTag<MemberList, NameTag>::member::vartype&
get() const
{
return FindMemberByNameTag<MemberList, NameTag>::member::access(data);
}
/** Compare two objects in a member-by-member way.*/
bool operator == (const PackedStruct& other) const
{
return MemberList::all_equal(data, other.data);
}
};
} // namespace packedstruct_detail
using packedstruct_detail::PackedStruct;
#endif // ifndef PACKEDSTUCT_HPP_INCLUDED
UND NUR HEUTE, GIBTS GRATIS DAZU: Ein Unit-Test, speziell zu geschnitten auf diese Klasse!
test_packedstruct.cpp
Code: Alles auswählen
// test_packedstruct.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
#include "packedstruct.hpp"
static int return_value = 0;
#define TEST_ASSERT(expr) if (!(expr)) \
{ \
std::cout<<"\n* Test assertion \""<< #expr <<"\" failed.\n Line "<< \
__LINE__<<" in file "<<__FILE__<<"\n\n"; \
return_value = EXIT_FAILURE; \
}
int main()
{
struct age {};
struct weight {};
struct cupsize {};
typedef PackedStruct<
unsigned short, age,
double, weight,
char, cupsize
> PackedTrivial;
std::cout<<"Struct has size: "<<sizeof(PackedTrivial)<<'\n';
// check struct sizes
TEST_ASSERT(sizeof(PackedTrivial) ==
sizeof(unsigned short) + sizeof(double) + sizeof(char)
);
// write to uninitialized object, check if written correctly
PackedTrivial trivial_uninit;
trivial_uninit.get<age>() = 19;
trivial_uninit.get<weight>() = 61.3;
trivial_uninit.get<cupsize>() = 'C';
std::cout<<"Member-assigned object:\n"
<<"Age: "<<trivial_uninit.get<age>()<<'\n'
<<"Weight: "<<trivial_uninit.get<weight>()<<'\n'
<<"Cupsize: "<<trivial_uninit.get<cupsize>()<<'\n'
<<'\n';
TEST_ASSERT(trivial_uninit.get<age>() == 19);
TEST_ASSERT(trivial_uninit.get<weight>() == 61.3);
TEST_ASSERT(trivial_uninit.get<cupsize>() == 'C');
// now check the memory manually
TEST_ASSERT(*reinterpret_cast<unsigned short*>(&trivial_uninit) == 19);
TEST_ASSERT(
*reinterpret_cast<double*>(
reinterpret_cast<char*>(&trivial_uninit) + sizeof(unsigned short)
) == 61.3
);
TEST_ASSERT(
*reinterpret_cast<char*>(
reinterpret_cast<char*>(&trivial_uninit) + sizeof(unsigned short) + sizeof(double)
) == 'C'
);
// copy objects with memcopy, assert that they are still the same
PackedTrivial other_trivial_uninit;
memcpy(&other_trivial_uninit, &trivial_uninit, sizeof(PackedTrivial));
TEST_ASSERT(trivial_uninit == other_trivial_uninit);
// check struct initialization
PackedTrivial trivial_init{21, 57.4, 'B'};
std::cout<<"Constructor-initialized object:\n"
<<"Age: "<<trivial_init.get<age>()<<'\n'
<<"Weight: "<<trivial_init.get<weight>()<<'\n'
<<"Cupsize: "<<trivial_init.get<cupsize>()<<'\n'
<<'\n';
TEST_ASSERT(trivial_init.get<age>() == 21);
TEST_ASSERT(trivial_init.get<weight>() == 57.4);
TEST_ASSERT(trivial_init.get<cupsize>() == 'B');
struct MyType {
MyType() = delete;
MyType(int x, double z) : _x{x}, _z{z}
{
std::cout<<"MyType constructed (this = "<<static_cast<void*>(this)<<") \n";
}
int _x;
double _z;
};
struct size {};
struct myval {};
typedef PackedStruct
<
std::size_t, size,
MyType, myval
> PackedNonTrivial;
# ifdef TEST_SHOULDBREAK
// this should not compile, since the MyType default constructor is deleted
PackedNonTrivial nontrivial;
# endif
// note that this is not packed anymore, since MyType is not a packed data
// structure. However, sizeof() and construction/access must still work.
PackedNonTrivial nontrivial{33, MyType{12, 99.999}};
std::cout<<"Constructor-initialized non-trivial object:\n"
<<"size: "<<nontrivial.get<size>()<<'\n'
<<"myval._x: "<<nontrivial.get<myval>()._x<<'\n'
<<"myval._z: "<<nontrivial.get<myval>()._z<<'\n'
<<'\n';
TEST_ASSERT(nontrivial.get<size>() == 33);
TEST_ASSERT(nontrivial.get<myval>()._x == 12);
TEST_ASSERT(nontrivial.get<myval>()._z == 99.999);
const PackedNonTrivial c_nontrivial{31, MyType{9, 879.999}};
TEST_ASSERT(c_nontrivial.get<size>() == 31);
TEST_ASSERT(c_nontrivial.get<myval>()._x == 9);
TEST_ASSERT(c_nontrivial.get<myval>()._z == 879.999);
# ifdef TEST_SHOULDBREAK
// error: read-only variable is not assignable
c_nontrivial.get<size>() = 12;
c_nontrivial.get<myval>()._x = 44;
c_nontrivial.get<myval>()._z = 93.122;
# endif
struct d{};
typedef PackedStruct
<
const std::size_t, size,
const double, d,
const char, cupsize
> PackedConstMembers;
PackedConstMembers const_members{22, 3.14, 'D'};
TEST_ASSERT(const_members.get<size>() == 22);
TEST_ASSERT(const_members.get<d>() == 3.14);
TEST_ASSERT(const_members.get<cupsize>() == 'D');
# ifdef TEST_SHOULDBREAK
// this should not compile, since the members are const
// error: read-only variable is not assignable
const_members.get<size>() = 4;
const_members.get<d>() = 6.28;
const_members.get<cupsize>() = 'C';
# endif
return return_value;
}
GREIFEN SIE ZU, MEINE DAMEN UND HERREN!
Übrigens habe ich dabei auch ein paar Tricks aus C++11 verwendet, das wichtigste dabei sind Variadic Templates. Wenns jemanden interessiert wie ich das Ding zusammengeschustert habe, kann sich den Code gern ansehen und dann einfach hier Fragen posten, ich helfe gerne.
mfg, fat-lobyte
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Haters gonna hate, potatoes gonna potate.