Classes and objects
A class puts data and the functions that work on it into one type. The data is private, so the only way to change it is through the member functions, and those functions can keep the object in a valid state. That is the whole idea: an object that cannot be put into an invalid state by accident.
The lesson writes a counter and an account, shows what this means inside a member function, and ends with the compiler error that makes private data private. Constructors, which decide how an object starts, are the next lesson.
A class with private data
class counter starts with private members by default; the public: label opens the part that callers may use. total_ is private, so main cannot read or write it directly, and add() and total() are the interface.
The trailing underscore in total_ is a naming convention for members, not a language rule. The = 0 is a default member initialiser, which gives the member a value when no constructor sets one; lesson 025 covers constructors and lesson 026 the member list.
The program calls add twice and reads the total once. The only code that touched total_ is inside the class, which is what makes the invariant of the next section possible.
#include <iostream>
class counter
{
public:
void add(int amount)
{
total_ += amount;
}
int total() const
{
return total_;
}
private:
int total_ = 0;
};
int main()
{
counter c;
c.add(3);
c.add(4);
std::cout << c.total() << '\n';
}
g++ -std=c++23 -Wall -Wextra class.cpp -o class_demo
./class_demo
7
this inside a member
Inside a member function, this is a pointer to the object the function was called on. It is rarely needed, because member names are looked up in the object automatically, and it is needed when a parameter hides a member.
The reset function takes a parameter called total_, the same name as the member. Without this, total_ = total_; assigns the parameter to itself; with this->total_ the left side is unambiguously the member.
A member function can also return *this to allow chaining, which operator overloading uses in lesson 030. For now this is only the way to name the object inside itself.
#include <iostream>
class counter
{
public:
void reset(int total_)
{
this->total_ = total_;
}
int total() const
{
return total_;
}
private:
int total_ = 0;
};
int main()
{
counter c;
c.reset(42);
std::cout << c.total() << '\n';
}
g++ -std=c++23 -Wall -Wextra this.cpp -o this_demo
./this_demo
42
Private means private
A member declared after private: can be used by the class and by nothing else. main is not a member, so the write below is rejected even though it is in the same file.
The error names the member and the class and says it is private within this context. The fix is a public member function that performs the change, not a friend declaration: the point of private data is that every change goes through code the class controls.
The same error appears when a derived class tries to reach a private base member; protected, which lesson 034 covers, is the access level for that case.
#include <iostream>
class counter
{
public:
void add(int amount)
{
total_ += amount;
}
int total() const
{
return total_;
}
private:
int total_ = 0;
};
int main()
{
counter c;
c.total_ = 5;
std::cout << c.total() << '\n';
}
g++ -std=c++23 -Wall -Wextra private.cpp -o private_demo
private.cpp: In function 'int main()':
private.cpp:23:7: error: 'int counter::total_' is private within this context
23 | c.total_ = 5;
| ^~~~~~
private.cpp:17:9: note: declared private here
17 | int total_ = 0;
| ^~~~~~
private.cpp:23:7: note: field 'int counter::total_' can be accessed via 'int counter::total() const'
23 | c.total_ = 5;
| ^~~~~~
| total()
An invariant the class keeps
An invariant is a statement about an object that is true whenever a caller can see it. For the account below it is that the balance is never negative: withdraw checks the amount before subtracting anything, and the balance can only change through that check.
The constructor sets the first balance, and the two calls show a withdrawal that succeeds and one that is refused. The refused call returns false, which the caller can handle without an exception and without inspecting the object.
Keeping the check inside the class is the difference between a data structure and an object. If balance_ were public, every caller would have to remember the rule, and one of them would eventually forget it.
#include <iostream>
class account
{
public:
account(int initial) : balance_(initial) {}
bool withdraw(int amount)
{
if (amount <= 0 || amount > balance_) {
return false;
}
balance_ -= amount;
return true;
}
int balance() const
{
return balance_;
}
private:
int balance_;
};
int main()
{
account a{100};
std::cout << a.withdraw(30) << ' ' << a.balance() << '\n';
std::cout << a.withdraw(90) << ' ' << a.balance() << '\n';
}
g++ -std=c++23 -Wall -Wextra invariant.cpp -o invariant
./invariant
1 70
0 70
struct and class differ in one default
A struct is a class whose members and bases are public by default; class makes them private. Nothing else differs: both can have member functions, constructors, inheritance and access specifiers.
The program uses a struct as a plain record with two public ints and initialises it with braces. That is the right shape for data that has no invariant to protect, such as a coordinate or a pair of sizes.
Use class when there is a rule to keep, and struct when the members are the interface. The standard library follows the same split: std::vector is a class, and std::pair is a struct.
#include <iostream>
struct point
{
int x = 0;
int y = 0;
};
int main()
{
point p{3, 4};
std::cout << p.x << ' ' << p.y << '\n';
}
g++ -std=c++23 -Wall -Wextra struct.cpp -o struct_demo
./struct_demo
3 4
Exercises
- Add a member function subtract(int) to counter that refuses to go below zero and returns a bool, like account::withdraw.
- Remove this-> from reset in this.cpp and print the total. What does the function assign to what?
- Turn point into a class with private x and y and add accessor functions. Which call in main stops compiling?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
privateprevent?Answer
Access from outside the class and its friends
The members are reachable only through the functions the class exposes.
-
What is an invariant of a class?
Answer
A rule about the state that every member function keeps true
The private data and the operations together keep the object in a valid state.
-
What is the only difference between
structandclass?Answer
The default access, public for struct and private for class
Both introduce a class type, and the access specifier before the first member is the default.
-
Why does the class make the data private?
Answer
So every change goes through a function that can check it
The member functions are the only path to the data, and they keep the invariant.
-
What does this program print?
#include <iostream> class counter { public: void add(int amount) { total_ += amount; } int total() const { return total_; } private: int total_ = 0; }; int main() { counter c; c.add(3); c.add(4); std::cout << c.total(); }Answer
7
The two calls go through the member function, which keeps the total, and the accessor prints 7.
-
What does the
thisprogram print?Answer
42
The member function uses this to reach the object it was called on.
-
What does this program print?
#include <iostream> class account { public: explicit account(int balance) : balance_(balance) {} bool withdraw(int amount) { if (amount > balance_) return false; balance_ -= amount; return true; } int balance() const { return balance_; } private: int balance_; }; int main() { account a{70}; std::cout << a.withdraw(10) << ' ' << a.balance() << " / "; std::cout << a.withdraw(100) << ' ' << a.balance(); }Answer
1 60 / 0 60
The first withdrawal leaves 60, and the second is refused, so the balance stays 60.
-
What does this program print?
#include <iostream> struct point { int x; int y; }; int main() { point p; p.x = 3; p.y = 4; std::cout << p.x << ' ' << p.y; }Answer
3 4
A struct is public by default, and the program sets and prints both members.
-
What does writing
c.total_ = 5;from main produce?Answer
'int counter::total_' is private within this context
The data member is private, and the message points at the public accessor as the way in.
-
Where can a private member be read from?
Answer
A member function of the same class
The access is per class, not per file or per object.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev