Sealatron wrote:
Just curious why struct==class is a benefit?
I didn't mean that the C struct is better than a C++ struct (or vice-versa). I tried to show the 3 things I know are used in the Handmade Hero code that are strictly C++. In fact, removing them and compiling under C is trivial, a testament to just how little of C++ Casey actually uses.
-----------------------------------------------
Now, to talk about the infamous struct (for interested newcomers):
A struct in pure C
Let's pretend I'm interested in making a data type for my 2D hero. I would experiment with loose variables that represent the properties of said hero:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | // Headers go here...
#define BOOL unsigned
#define TRUE 1
#define FALSE 0
#define INITIAL_HERO_SIZE 6 // Feet
int main()
{
// Our hero, Muratori!
char *name = "Muratori";
BOOL is_alive = TRUE;
int x = 0;
int y = 0;
int x_step = 1;
int y_step = 1;
unsigned size = INITIAL_HERO_SIZE;
BOOL should_reset = FALSE;
while (TRUE)
{
if (should_reset) // Assume flag handled elsewhere
{
name = "Muratori";
is_alive = TRUE;
x = 0;
y = 0;
x_step = 1;
y_step = 1;
size = INITIAL_HERO_SIZE;
}
// Play game...
}
return 0;
}
|
Notice we're setting these values to their initial state when resetting, and we're probably "resetting our hero" in other places throughout the lifetime of our game. We can compress these operations into a function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 | // Headers go here...
#define BOOL unsigned
#define TRUE 1
#define FALSE 0
#define INITIAL_HERO_SIZE 6 // Feet
void reset_hero(char *name,
BOOL *is_alive,
int *x,
int *y,
int *x_step,
int *y_step,
unsigned *size)
{
if (name)
{
free(name);
}
name = strdup("Muratori");
assert(name);
*is_alive = TRUE;
*x = *y = 0;
*x_step = *y_step = 1;
*size = INITIAL_HERO_SIZE;
}
int main()
{
// Our hero, Muratori!
char *name;
BOOL is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
reset_hero(name, &is_alive, &x, &y, &x_step, &y_step, &size);
BOOL should_reset = FALSE;
while (TRUE)
{
if (should_reset) // Assume flag handled elsewhere
{
reset_hero(name, &is_alive, &x, &y, &x_step, &y_step, &size);
}
// Play game...
}
return 0;
}
|
Wait. Were we not talking about what a dang
struct is?
Using a struct
The
struct keyword is used to specify a block of memory, able to store arbitrary values of potentially different data types, under a single name:
| struct Hero
{
char *name;
BOOL is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
};
|
Here I'm telling the compiler that there may be a block of memory, referred to as Hero, that can store seven different values: pointer to a
char, a
boolean, four
ints, and an
unsigned int. Let us replace those loose variables in the previous code, and use this struct instead:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 | // Headers go here...
#define BOOL unsigned
#define TRUE 1
#define FALSE 0
#define INITIAL_HERO_SIZE 6 // Feet
struct Hero
{
char *name;
BOOL is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
};
void reset_hero(char *name,
BOOL *is_alive,
int *x,
int *y,
int *x_step,
int *y_step,
unsigned *size)
{
if (name)
{
free(name);
}
name = strdup("Muratori");
assert(name);
*is_alive = TRUE;
*x = *y = 0;
*x_step = *y_step = 1;
*size = INITIAL_HERO_SIZE;
}
int main()
{
struct Hero muratori;
reset_hero(muratori.name,
&muratori.is_alive,
&muratori.x,
&muratori.y,
&muratori.x_step,
&muratori.y_step,
&muratori.size);
BOOL should_reset = FALSE;
while (TRUE)
{
if (should_reset) // Assume flag handled elsewhere
{
reset_hero(muratori.name,
&muratori.is_alive,
&muratori.x,
&muratori.y,
&muratori.x_step,
&muratori.y_step,
&muratori.size);
}
// Play game...
}
return 0;
}
|
We can compact this even further, because a function can take in a
struct as an argument:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | // Headers go here...
#define BOOL unsigned
#define TRUE 1
#define FALSE 0
#define INITIAL_HERO_SIZE 6 // Feet
struct Hero
{
char *name;
BOOL is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
};
void reset_hero(struct Hero *hero)
{
if (hero->name)
{
free(hero->name);
}
hero->name = strdup("Muratori");
assert(hero->name);
hero->is_alive = TRUE;
hero->x = hero->y = 0;
hero->x_step = hero->y_step = 1;
hero->size = INITIAL_HERO_SIZE;
}
int main()
{
struct Hero muratori;
reset_hero(&muratori);
BOOL should_reset = FALSE;
while (TRUE)
{
if (should_reset) // Assume flag handled elsewhere
{
reset_hero(&muratori);
}
// Play game...
}
return 0;
}
|
You might be passing in
struct Hero to many functions. You can
typedef the
struct to an alias, just like you would with any other type, and use that alias instead:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | // Headers go here...
#define BOOL unsigned
#define TRUE 1
#define FALSE 0
#define INITIAL_HERO_SIZE 6 // Feet
typedef struct Hero
{
char *name;
BOOL is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
} H;
void reset_hero(H *hero)
{
if (hero->name)
{
free(hero->name);
}
hero->name = strdup("Muratori");
assert(hero->name);
hero->is_alive = TRUE;
hero->x = hero->y = 0;
hero->x_step = hero->y_step = 1;
hero->size = INITIAL_HERO_SIZE;
}
int main()
{
struct Hero muratori;
reset_hero(&muratori);
BOOL should_reset = FALSE;
while (TRUE)
{
if (should_reset) // Assume flag handled elsewhere
{
reset_hero(&muratori);
}
// Play game...
}
return 0;
}
|
Maybe you know you'll
always want to use an alias. In that case, you could omit naming the struct:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | // Headers go here...
#define BOOL unsigned
#define TRUE 1
#define FALSE 0
#define INITIAL_HERO_SIZE 6 // Feet
typedef struct
{
char *name;
BOOL is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
} Hero; // 'Hero' is now the alias for this unnamed struct.
void reset_hero(Hero *hero)
{
if (hero->name)
{
free(hero->name);
}
hero->name = strdup("Muratori");
assert(hero->name);
hero->is_alive = TRUE;
hero->x = hero->y = 0;
hero->x_step = hero->y_step = 1;
hero->size = INITIAL_HERO_SIZE;
}
int main()
{
Hero muratori;
reset_hero(&muratori);
BOOL should_reset = FALSE;
while (TRUE)
{
if (should_reset) // Assume flag handled elsewhere
{
reset_hero(&muratori);
}
// Play game...
}
return 0;
}
|
Notice that while explaining what a struct is, we used it to compress our silly code nicely.
A struct in C++
Let's talk about a
class first. A simple, rough definition is that the
class keyword allows you to specify (1) a collection of data, (2) operations that can be performed on that data, and (3) related functions, all under a single name. You can restrict access to certain types of data or functions, so it is only used internally. They can have the concept of a lifespan, inheritance, templates, and a ghastly number of so many other things. We will not cover how all of this works, just know that it is its own thing. This is purely C++, not C.
You can actually specify a class with two keywords:
class or
struct. Their difference was succintly explained by
d7samurai:
struct and class are the exact same thing, except for one aspect: A class is private by default and a struct is public by default.
Thus, in C++, there is no concept of a struct, but a class. struct is a class.
Now, if you define a class that is completely public to access its data, and stick to only having variables inside them, and decide to use the
struct keyword to have it public by default and save some typing,
then you kinda made it into a C struct. You constrained the use of that class to the point of it being treated exactly like one.
But since it's a class, and not a C struct, I can loosen up that constraint and maybe add a function in there:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 | // Headers go here...
#define INITIAL_HERO_SIZE 6 // Feet
struct Hero
{
char *name;
bool is_alive;
int x;
int y;
int x_step;
int y_step;
unsigned size;
void reset()
{
if (name)
{
free(name);
}
name = strdup("Muratori");
assert(name);
is_alive = true;
x = y = 0;
x_step = y_step = 1;
size = INITIAL_HERO_SIZE;
}
}
int main()
{
Hero muratori;
muratori.reset();
bool should_reset = false;
while (true)
{
if (should_reset) // Assume flag handled elsewhere
{
muratori.reset();
}
// Play game...
}
return 0;
}
|
Notice the above code will not compile under a C compiler, because C does not have classes (or a native bool type).