Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

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 struct are public by default. Members of a class are 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: struct members default to public, class members to private.
  • Everything else — constructors, methods, inheritance — works identically in both.
  • Convention: struct for plain data, class when something must be enforced.
  • A struct with all-public members and no constructor is an aggregate and supports Book{...} 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.