Lesson 1 of 12
You have used struct since Module 6 to group related values. class does the same job. The
difference between them is one word:
Members of a
structare public by default. Members of aclassare private by default.
That is the entire language-level difference. Everything else you may have heard is convention.
#include <iostream>
struct Point {
int x; // public
int y; // public
};
class Secret {
int hidden; // private — unreachable from outside
public:
void set(int v) { hidden = v; }
int get() const { return hidden; }
};
int main() {
Point p;
p.x = 3; // fine: public
p.y = 4;
std::cout << p.x << "," << p.y << "\n";
Secret s;
// s.hidden = 5; // error: 'int Secret::hidden' is private
s.set(5);
std::cout << s.get() << "\n";
return 0;
}
3,4
5
Swap the two keywords and add the matching public: / private: labels, and the programs behave
identically. The compiler does not treat them as different kinds of thing.
The convention that grew around it
Because the defaults differ, the two keywords came to signal different intentions:
| Use struct when | Use class when |
|---|---|
| It is a bundle of data | It has behaviour and invariants |
| All members are public | Some state is private |
| There is nothing to protect | The object must stay valid |
A struct is a plain data holder: a Point, a Colour, a parsed row from a file. Anyone can
read and write any field, and no field's value depends on another's.
A class enforces something. Lesson 9.6 calls that something an invariant — a rule that
must always be true of a valid object, such as "size never exceeds capacity."
Nothing enforces this convention. It is a signal to the human reading your code, and worth following for that reason alone.
Both can do everything
There is no capability locked behind one keyword:
#include <iostream>
// A struct with a constructor, a private member, and a method — all legal.
struct Counter {
Counter(int start) : count(start) {}
void tick() { ++count; }
int value() const { return count; }
private:
int count;
};
int main() {
Counter c(10);
c.tick();
c.tick();
std::cout << c.value() << "\n";
return 0;
}
12
That compiles and works. It is also confusing to read, because it looks like a data bundle and
behaves like a class. Follow the convention: this should have been class Counter with a
public: section.
Aggregate initialisation
A struct with all-public members and no user-written constructor is an aggregate, which
gets brace initialisation for free:
#include <iostream>
#include <string>
struct Book {
std::string title;
int year;
double price;
};
int main() {
Book b{"Dune", 1965, 9.99}; // aggregate initialisation, in order
std::cout << b.title << " (" << b.year << ") $" << b.price << "\n";
Book empty{}; // all members value-initialised
std::cout << "[" << empty.title << "] " << empty.year << " " << empty.price << "\n";
return 0;
}
Dune (1965) $9.99
[] 0 0
Note the order matters and is silent about mistakes: Book{"Dune", 1965, 9.99} is right, but a
struct with two adjacent int fields will happily accept them swapped. C++20's designated
initialisers make it explicit:
#include <iostream>
#include <string>
struct Book {
std::string title;
int year;
double price;
};
int main() {
Book b{.title = "Dune", .year = 1965, .price = 9.99};
std::cout << b.title << " " << b.year << "\n";
return 0;
}
Dune 1965
Add a constructor and the type stops being an aggregate, so brace initialisation then goes through that constructor instead. Lesson 9.3 covers constructors properly.
Which to reach for
Default to struct for anything that is purely data — it is shorter and says "no secrets here."
Reach for class the moment some member must be protected from arbitrary modification.
If you find yourself writing a struct where every field has a getter and a setter that do
nothing but read and write, you have written a class badly. Either the fields should be public
(it is a struct) or the getters and setters should enforce something (it is a class).
Try it
Write a struct Rectangle with public width and height and a function that computes its
area. Then write a class Circle that keeps its radius private, rejects negative values, and
exposes area(). Note which one could be left in an invalid state.
Checkpoint
- The only language difference:
structmembers default to public,classmembers to private. - Everything else — constructors, methods, inheritance — works identically in both.
- Convention:
structfor plain data,classwhen something must be enforced. - A
structwith all-public members and no constructor is an aggregate and supportsBook{...}initialisation; C++20 designated initialisers name the fields. - Getters and setters that only read and write a field mean you wanted a
struct.
Questions about this lesson
Log in to ask a question.