Reputation: 75
I'm a little confused about the best way to initialize complex structs (struct of struct)
I basically have a setup like this
typedef struct {
float x;
float y;
} Vectorf;
typedef struct {
Vectorf position;
Vectorf direction;
Vectorf velocity;
} Player;
What would be the best way to initialize a Player object?
Variant A
Vectorf position = {1.0f, 1.0f};
Vectorf direction = {1.0f, 0.0f};
Vectorf velocity = {0.0f, 0.0f};
Player player = {position, direction, velocity};
Variant B
Player *player = malloc(sizeof(Player));
player->position.x = 1.0f;
player->position.y = 1.0f;
player->direction.x = 1.0f;
player->direction.y = 0.0f;
player->velocity.x = 0.0f;
player->velocity.y = 0.0f;
--- Stuff ---
free(player);
Or even create a function like
Player *createPlayer(float px, float py...)
Player createPlayer(float px, float py...)
But inside these I would need Variant A or B again I guess.
Is it just a matter of taste or are there benefits for either?
I also have something like this
typedef struct {
int x;
int y;
Texture *texture;
bool walkable;
} Tile;
typedef struct {
int width;
int height;
Tile *tiles;
} Map;
Here a create function seems more reasonable, because I can pass actual map data.
Upvotes: 4
Views: 535
Reputation: 13580
I think that answer should be depends on your needs, both ways of creating objects are OK.
Sometimes you don't need to worry about allocation memory with malloc
if you
are going to use an object only a couple of times and you don't need that the
objects "survives" when the function exits. So your variant A would be OK.
Sometimes you want to create a lot of objects and store them in other data structures (lists, trees, etc), and you need the objects to "live" throughout the whole program. In this case your variant B would be the better. AnT's answer shows you how you could save lines of code for initialization.
The other time when I consider using malloc
is
when I know that the struct is very large and would consume lots of bytes for
each object, so having a lot of them could potentially eat up all your stack. In
that case I'd rather have these large objects in the heap, and also handling
with pointers is much cheaper than having to creates copies when calling
function that do not take pointers.
I also use malloc
when I see the structs as classes and I want a clean API for
creating, using and destryoing them. I always use prefix for the functions and I
always have a create, init and free function for every object type, something
like this:
typedef struct abc {
// lot's of members
} Abc;
Abc *abc_create(void);
int abc_init(Abc *abc); // always with default values
void abc_free(Abc *abc);
int abc_do_A(Abc *abc, int x);
int abc_do_B(Abc *abc, int y);
....
and the three first functions usually look like this:
Abc *abc_create(void)
{
Abc *abc = calloc(1, sizeof *abc);
if(abc == NULL)
return NULL;
if(abc_init(abc) == 1)
return abc;
free(abc);
return NULL;
}
int abc_init(Abc *abc)
{
if(abc == NULL)
return 0;
// initializations
...
return 1;
}
void abc_free(Abc *abc)
{
if(abc == NULL)
return;
// do free of other malloced
// members if present
free(abc);
}
I think this gives you a very clear and easy API to use. I maintain a C library at work where I have at least 50+ of these structs, all with the same scheme, using them it's easy because they all behave like this:
Abc *abc = abc_create(1, 2, 3);
if(abc == NULL)
{
error handling
}
abc_do_A(abc, 12);
abc_do_A(abc, 11);
...
abc_free(abc); // when not needed anymore
Upvotes: 2
Reputation: 320541
For Variant A
Player player = { { 1., 1. }, { 1., 0. }, { 0., 0. } };
You can also use a tagged variant for better clarity and readability, as in @dev_null's answer.
For Variant B compound literals will help you
Player *player = malloc(sizeof *player);
*player = (Player) { { 1., 1. }, { 1., 0. }, { 0., 0. } };
If you have a memdup
-like functionality at your disposal, you can implement Variant B as an one-liner
/* Assuming `void *memdup(const void *src, size_t size);` */
Player *player = memdup(
&(Player) { { 1., 1. }, { 1., 0. }, { 0., 0. } },
sizeof *player);
Again, feel free to use a tagged version, if you like it better.
Compound literals allow you to mix-and-match your original approach and {}
-based approach, if you for some reason decide that it is beneficial
Player *player = malloc(sizeof *player);
player->position = (Vectorf) { 1., 1. };
player->direction = (Vectorf) { 1., 0. };
player->velocity = (Vectorf) { 0., 0. };
Upvotes: 0
Reputation: 1997
Player player = {.position = {1.0f, 1.0f},
.direction = {1.0f, 0.0f},
.velocity = {0.0f, 0.0f}};
Note this won't work in C++. Only in C.
Upvotes: 2