Reputation: 11893
let's say i have a few different structure definitions (in fact, i have arround 50 such definitions):
struct Type1{
int i;
float f;
};
struct Type2{
bool b1;
bool b2;
double d;
};
they are all POD, but can contain completely different data.
now, at runtime, i want to decide what type of those i need and then create an array (or vector) of this chosen type, such that all the data is layed out continguously in the memory.
How would I do that?
also - let's say i have an integer (it contains some flags) which determines what type of those structs i need. Is there any way how i could arrange the typedefinitions of those structs into a hashmap or so such that i can do only something like:
vector<myTypeHashMap[flagsInt]> myVect;
?
I know this goes rather thorwards metaprogramming (of which i have no clue :) ), but maybe there's a way to do that?
thanks
thanks
Upvotes: 0
Views: 830
Reputation: 3481
It sounds like Boost.Any could be of potential use, at least to get an idea of how to satisfy your needs.
Upvotes: 0
Reputation: 57728
According to your stated requirements, you will need a Factory that creates the objects that you want. The Factory could be as simple as a std::map<*struct name*, *pointer to creation function*>
. However, you will also need some kind of object containing operations that can be performed on the mystery object during run-time (which is another topic).
In order for the Factory to work, you either need to have all the objects derived from a common base class or use a void *
pointer to refer to them. (Looking like generic programming techniques here...)
Most of the Factory design patterns return pointers to mystery objects. It returns a pointer to an instance of a base class; but all you know is that it follows the interface defined in the base class, thus it is a mystery object. The oracle says that you will need to know the kind of object the factory generates in order to perform special actions on the objects. Most of your program will be if object is *this* type, perform *special action*
.
As Neil stated, this is where the re-design comes in. Change the perspective to the Object's point of view. The object determines everything. Thus the methods should belong to the object such that it is self-sufficient. For example, once the Factory creates an object, the object will read in its data and display annotations and results. If a calculate
method is common to all objects, then it becomes a pure virtual method of the base class, forcing all descendants to have an implementation. The nice part is that you can iterate over an array of pointers to the base class and execute this calculate
method without needing to know the actual implementation.
struct CommonBase
{
virtual ResultType calculate(void) = 0;
};
struct Type1 : CommonBase
{
int i;
float f;
ResultType calculate(void); // performs calculation using *i* and *f*
};
struct Type2{
bool b1;
bool b2;
double d;
ResultType calculate(void); // performs calculation using *b1*, *b2* and *d*
};
//...
std::vector<CommonBase *> the_objects;
//...
std::vector<CommonBase *>::iterator iter;
for (iter = the_objects.begin();
iter != the_objects.end();
++iter)
{
ResultType result;
result = (*iter)->calculate();
std::cout << "Result: " << result << "\n";
}
Upvotes: 0
Reputation: 40319
In my opinion, you are best off going down a C route instead of a C++ route.
This is how I would solve your problem, without knowing more about the specific domain:
void *out_buf;
write_type(struct &a)
{
out_buf = malloc(sizeof(a));
memcpy(out_buf, a, sizeof(a));
}
write_type(struct &b); //so forth and so on.
I don't particularly know what you're trying to accomplish, so this may be a bit hard to fit in. At any rate, 50 types means a lot of code, whatever you do.
Upvotes: 0
Reputation: 728
Trying to flesh out drspod's answer above, let's say you make the following helper class:
class HelperBase {
virtual void *MakeArray(int n) = 0;
virtual void *Get(void *array, int i) = 0;
}
template <typename T> class Helper {
void *MakeArray(int n) { return new T[n]; }
void *Get(void *array, int i) { return &(((T*)array)[i]); }
}
Now you have a map from integers (or flags, or whatever) to HelperBase
s:
std::map<int, HelperBase*> GlobalMap;
GlobalMap[1] = new HelperBase<Type1>;
GlobalMap[2] = new HelperBase<Type2>;
// Don't forget to eventually delete these (or use smart pointers)
Now at runtime you can say
void *array = GlobalMap[someIndex].MakeArray(n);
void *thirdElement = GlobalMap[someIndex].Get(2);
Upvotes: 0
Reputation: 10184
Why do you need them to be contiguous in memory?
I suspect that you would be better off simply creating an array of pointers, and then allocating the classes at runtime dynamically. Then you can set the pointers in the array to point to what you've created.
Create Classes - Why are you using structs and not classes? I would just create a superclass for all the types you want, subclasses for each different type, an array (or list or collection) of pointers to the objects, and you can create them at runtime.
Struct Pointer Solution - If you must use structs, you could create a struct that has a tag (to identify the type) and a union of pointers to the different structs. That would be type safe to access, no casting required, and the tag would help prevent code errors. This will be more memory efficient, as if you create a union of the actual structs, you will have to allocate memory for the largest one.
Struct Solution - If you really do need it contiguous in memory, then create a struct with a tag id and a union of the different structs that you want.
Ideally, you would have a superclass for all the different types, then you
Upvotes: 0
Reputation: 545885
How do you select which type to use at runtime? You haven’t mentioned this and with my knowledge of C++ I don’t see an easy way to do this at all.
Anyway, once you’ve selected your choice type at runtime you can also determine its size (using sizeof
) and consequently create an array:
size_t const size = sizeof(your_type);
char* buffer = new char[size * count];
your_type* = reinterpret_cast<your_type*>(buffer);
Now, this code works, but is completely useless because it needs to know the type as your_type
, and then you could of course just say new your_type[count]
. You could, however, create a mapping of your types to their sizes, using the integer flags you proposed:
enum type { tfoo, tbar };
std::map<type, size_t> type_sizes;
type_sizes[tfoo] = sizeof(foo);
type_sizes[tbar] = sizeof(bar);
// …
char* buffer = new char[type_sizes[type_index] * count];
However, for practical solutions, you should rely on inheritance to compose a type hierarchy, and then perhaps a factory method as mentioned by others.
PS: Since you want to have this behaviour at runtime, template metaprogramming actually has got nothing to do with it, quite the opposite: metaprogramming is executed at compile time.
Upvotes: 0
Reputation: 57056
You aren't going to be able to do what you want while using compile-time tools like templates.
What you probably have to do is handle the memory yourself. Create a class that will hold a number (number of elements in the array), and a char *
pointer. Given a desire for N structs of type T, and an int size[]
array giving the sizes for the various Ts, allocate the memory with new char(N * size[T])
. Store the size, and you're ready to go.
To access the memory, you can use operator[]
, and return a void *
, or a master struct like outlined below. (What you return has to be specified at compile time, so you can't use any of the fifty-odd struct types.)
At that point, you need to have a functions that will turn raw bytes into whatever fields you like, and vice versa. These can be called by the operator[]
function. You can't do anything with a struct that doesn't have a type determined at compile time, so you'll probably want a master struct with lots of fields, so it can handle all the int
s, all the bool
s, all the float
s, all the double
s and so forth any of your structs is going to have. You will of course need tables to show what fields are valid.
This is a whole lot of work, and I'd have to ask if it's really necessary. Is all this really going to buy you anything useful?
Upvotes: 0
Reputation: 7157
You could use some kind of factory. Search google for "factory pattern c++".
Some simple example code to explain it:
enum TypeCode { FOO, BAR, ... };
void* makeInstance( TypeCode t ) {
switch( t ) {
case FOO: return new FooStruct;
case BAR: return new BarStruct;
...
}
}
void* makeArray( TypeCode t, size_t len ) {
switch( t ) {
case FOO: return new FooStruct[len];
case BAR: return new BarStruct[len];
...
}
}
EDIT Example OOP-style mapping of TypeCode
to some functionality and type description:
// base class .. you may add common functionality
class TypeTraitBase {
public:
virtual void* newStruct() const = 0;
virtual void* newArray( size_t len ) const = 0;
virtual size_t getSize() const = 0;
virtual TypeCode getCode() const = 0;
// add whatever else you need.. e.g.
virtual bool isNumeric() const { return false; }
};
template <TypeCode TCode, typename TStruct>
class TypeTrait : public TypeTraitBase {
public:
virtual TStruct* newStruct() const { return new TStruct; }
virtual TStruct* newArray( size_t len ) const { return new TStruct[len]; }
virtual size_t getSize() const { return sizeof(TStruct); }
virtual TypeCode getCode() const { return TCode; }
};
/* OPTIONAL...
// you may add specializations for some types
// - though it is required only if you want something like isNumeric(),
// - newStruct, newArray and so on do not require specializations!
template < INTEGER, IntegerStruct >
class TypeTrait : public TypeTraitBase {
public:
virtual TStruct* newStruct() const { return new IntegerStruct; }
virtual TStruct* newArray( size_t len ) const { return new IntegerStruct[len]; }
virtual size_t getSize() const { return sizeof(IntegerStruct); }
virtual TypeCode getCode() const { return INTEGER; }
virtual bool isNumeric() const { return true; }
};
*/
class TypeTraitMgr {
static std::map<TypeCode,TypeTraitBase*> traits;
public:
static void reg( TypeTraitBase* tt ) { traits[tt->getCode()] = tt; }
static void cleanup() { /* delete all TypeTraits in traits */ }
static TypeTraitBase* get( TypeCode code ) { return traits[code]; }
};
// in .cpp file: instantiate the static member:
std::map<TypeCode,TypeTraitBase*> traits;
// somewhere before you use it, register all known types:
TypeTraitMgr::reg( new TypeTrait<FOO,YourFOOStruct> );
TypeTraitMgr::reg( new TypeTrait<BAR,YourBARStruct> );
// use it...
void* foo = TypeTraitMgr::get( FOO )->newStruct();
size_t size_of_foo = TypeTraitMgr::get( FOO )->getSize();
// on shutdown, cleanup type traits (they were allocated on heap, delete required)
TypeTraitMgr::cleanup();
This code was not tested, it may contain bugs ;)
Note, that this solution has some overhead of virtual function calls and the like. But its acceptable.
Also, it may be a good idea to merge TypeTraits directly into you structs. This will result in less typing, less code, less overhead.
Upvotes: 1
Reputation: 20495
you could use a union struct and std::vector (note: this is an old-school c technique and is not meant for 50 objects :D). The unifying struct could like this:
struct unifier
{
int type;
union
{
struct A;
struct B;
};
};
If their isn't a large discrepancy in size isn't too large, this method allows you to mix and match types, or if you use only one type this mechanism will allow you to reuse the memory for different objects.
Upvotes: 0
Reputation: 20495
You can use RTTI to write a switch statement to base decisions on the type of an object -- at runtime.
You can also use the trait pattern to assign meta-information to your types: traits. Once you have created the generic trait and/or the specialized traits for your types you can wrap the std::vector container with another template to create your contigious memory object. You would have to create 50 specializations though so try to restrict yourself to a generic trait.
I think sticking entirely to my first tip will give you better mileage as you don't want to instantiate 50 generic objects for your type.
Upvotes: 0
Reputation: 386
You want a function to create an array, templated by the type?
template <typename T>
T* makeArray( int n )
{
return new T[n];
}
...
Type1* arrayoftype1 = makeArray<Type1>( 10 );
delete[] arrayoftype1;
Upvotes: 0
Reputation: 2182
You can do something like this:
void * buffer;
if (a) {
buffer = new Type1[30];
} else {
buffer = new Type2[30];
}
Upvotes: 0