The real difference between struct and class
“Should I use a struct
or a class
?”
Such is the question many C++ programmers ask themselves, or ask around to more experienced co-workers, when designing their code.
There is sometimes a cloud of misconception about what the difference between struct
and class
technically is, particularly amongst the youngest developers. And once we get to understand the technical difference, some degree of uncertainty often remains about which one to use in a given context. Sometimes developers even disagree about which is the more appropriate in their code.
Let’s start by clearing up the situation, by stating the technical difference between struct
and class
, and then propose rules to choose between the two, by looking at what the C++ Core Guidelines written by the Jedis of C++ have to say about it.
The legal difference
In terms of language, except one little detail, there is no difference between struct
and class
. Contrary to what younger developers, or people coming from C believe at first, a struct
can have constructors, methods (even virtual ones), public, private and protected members, use inheritance, be templated… just like a class
.
The only difference is if you don’t specify the visibility (public, private or protected) of the members, they will be public in the struct
and private in the class
. And the visibility by default goes just a little further than members: for inheritance if you don’t specify anything then the struct
will inherit publicly from its base class:
struct T : Base // same thing as "struct T : public Base" { ... };
while the class
will do private inheritance:
class T : Base // same thing as "class T : private Base" { ... };
That’s it. No other difference.
Once we get past this language precision, the following question arises: if struct
and class
are so similar, when should I use one or the other?
The real difference between struct
and class
: what you express by using them
The difference that really matters between struct
and class
boils down to one thing: convention. There are some conventions out there that are fairly widespread and that follow a certain logic. Following these conventions gives you a way to express your intentions in code when designing a type, because as we’ll see in a moment, implementing it as a struct
doesn’t convey the same message as implementing it as a class
.
struct
In a word, a struct
is a bundle. A struct
is several related elements that needed to be tied up together in a certain context. Such a context can be passing a restricted number of arguments to a function:
struct Point { double x; double y; }; void distance(Point p1, Point p2);
Although it’s a bundle, struct
can be used to effectively raise the level of abstraction in order to improve the code: in the above example, the distance
function expects Points rather than doubles. And on the top of this, the struct
also has the benefit of logically grouping them together.
Another context is returning several values from a function. Before C++17 and structured bindings, returning a struct
containing those values is the most explicit solution. Have a look at Making your functions functional for more about making function interfaces clearer.
class
In two words, a class can do things. A class has responsibilities. These responsibilities can be quite simple, like retrieving data that the class may even contain itself. For this reason, you want to use the term class
when you are modelling a concept (that has an existence in the business domain or not), the concept of a object that can perform actions.
Contrary to a struct
, a class is made to offer an interface, that has some degree of separation from its implementation. A class
is not just there to store data. In fact a user of a class is not supposed to know what data the class is storing, or if it contains any data at all for that matter. All he cares about is its responsibilities, expressed via its interface.
A class
raise the level of abstraction between interface and implementation even more than a struct
does.
Sometimes a type that was initially implemented as a struct
ends up turning into a class
. This happens when you realize the various bits that were bundled turn out to form a higher level concept when they are considered together, or have a stronger relation than what was perceived initially.
This is where invariants come into play. An invariant is a relation between the data members of a class that must hold true for the methods to work correctly. For example, a std::string
can hold a char*
and a size
in its implementation (well at least conceptually, since modern string implementations are more complex than that due to optimizations). Then an invariant is that the number of characters in the allocated char
buffer must match the value in the size
member. Another invariant is that the char*
is initialized and points to valid memory.
Invariants are set into place by the constructor of the class
and the methods make the assumption that all the invariants hold true when they are called, and ensure they remain true when they finish. This can be a tacit agreement, or, as has been discussed for standardization, such pre-conditions and post-conditions in methods could one day be explicitly stated in code, and checked at run-time.
Finally a simple rule of thumb for choosing between struct
or class
is to go for class
whenever there is at least one private member in the structure. Indeed, this suggests that there are implementation details that are to be hidden by an interface, which is the purpose of a class.
The C++ Core Guidelines
The above has been inspired by the C++ Core Guideline (which is a great read by the way), in particular the following:
Related articles:
- Respecting levels of abstraction
- A Summary of the Metaclasses Proposal for C++
- Make your functions functional
Share this post!