Data abstraction, encapsulation, constructors, and the this pointer. How C++ lets you define your own types.
You're building a bookstore application. Each book has an ISBN, a number of units sold, and total revenue. You could use three separate variables — but then every function needs three parameters, and keeping them in sync is a nightmare. What if you could bundle them into one thing?
A class lets you define your own type. It bundles data (the ISBN, units sold, revenue) and operations (compute average price, combine two sales records) into a single entity. Users of the class work with the operations; the data stays hidden inside.
A struct exposes everything by default. A class hides everything by default. Click to toggle between them and see which members are accessible.
struct and a class in C++?When you call a member function on an object, how does the function know which object's data to use? Every member function receives a hidden parameter called this — a pointer to the object the function was called on.
c++ struct Sales_data { string isbn() const { return bookNo; } // compiler sees this as: // string isbn(const Sales_data *const this) { return this->bookNo; } string bookNo; unsigned units_sold = 0; double revenue = 0.0; };
When you write total.isbn(), the compiler passes &total as the this pointer. Inside isbn, using bookNo really means this->bookNo.
Member functions can return *this to enable chaining:
c++ Sales_data& combine(const Sales_data &rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; // return the object this function was called on } // enables: total.combine(trans1).combine(trans2);
this inside a member function?A member function with const after the parameter list promises not to modify the object. The compiler enforces this by making this a pointer to const:
c++ struct Sales_data { string isbn() const { return bookNo; } // this has type: const Sales_data *const // can read members but cannot modify them };
Why does this matter? If you have a const Sales_data object, you can only call const member functions on it. Without the const qualifier, isbn() would be uncallable on const objects.
const. This makes the function usable on both const and non-const objects — maximum flexibility.You can overload a member function based on whether the object is const:
c++ class Screen { public: Screen &display(ostream &os) { do_display(os); return *this; } const Screen &display(ostream &os) const { do_display(os); return *this; } private: void do_display(ostream &os) const { os << contents; } };
const object call a non-const member function?Encapsulation hides a class's implementation from its users. Users interact only through the public interface. The implementation can change without breaking any code that uses the class.
| Specifier | Effect |
|---|---|
public | Accessible to all code |
private | Accessible only by member functions and friends |
protected | Accessible by members, friends, and derived classes (Ch 15) |
c++ class Sales_data { public: // interface Sales_data() = default; string isbn() const { return bookNo; } Sales_data &combine(const Sales_data&); private: // implementation string bookNo; unsigned units_sold = 0; double revenue = 0.0; };
private?A constructor initializes a newly created object. It has the same name as the class and no return type. Every class needs at least one. If you don't write any, the compiler generates a synthesized default constructor.
c++ class Sales_data { public: Sales_data() = default; // synthesized default Sales_data(const string &s): bookNo(s) {} // initializer list Sales_data(const string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p * n) {} Sales_data(istream &is); // reads from stream private: string bookNo; unsigned units_sold = 0; // in-class initializer double revenue = 0.0; };
The colon after the parameter list introduces the constructor initializer list. Each member is initialized directly with the given value. This is more efficient than assigning inside the body, because it initializes rather than default-constructs-then-assigns.
= default tells the compiler to generate the default constructor even though you've defined other constructors. Without it, defining any constructor suppresses the synthesized default constructor.
Sometimes a non-member function or another class needs access to private members. Instead of making the data public, you can declare that function or class as a friend:
c++ class Sales_data { friend Sales_data add(const Sales_data&, const Sales_data&); friend ostream &print(ostream&, const Sales_data&); friend istream &read(istream&, Sales_data&); public: // ... constructors and member functions ... private: string bookNo; unsigned units_sold = 0; double revenue = 0.0; };
You can also declare an entire class as a friend, giving all of its member functions access to the private members of the granting class:
c++ class Screen { friend class Window_mgr; // Window_mgr can access Screen's privates // ... };
friend declaration do?Let's visualize how a class object actually lives in memory. A Sales_data object occupies a contiguous block on the stack (or heap). Each data member is laid out in order, with potential padding bytes added by the compiler for alignment.
See how Sales_data's members are laid out in memory. The string occupies 32 bytes (internal pointer, size, capacity), followed by padding, then the unsigned int and double.
sizeof. That's why calling isbn() needs the this pointer — the function is separate from the object.Sometimes a piece of data belongs to the class itself, not to any particular object. A static member exists once, regardless of how many objects exist. Think of it as a class-wide variable.
c++ class Account { public: void calculate() { amount += amount * interestRate; } static double rate() { return interestRate; } static void rate(double); private: string owner; double amount; static double interestRate; // shared by ALL Account objects static double initRate(); };
| Regular member | static member |
|---|---|
| One copy per object | One copy for entire class |
Accessed via object: obj.x | Accessed via class: Account::rate() |
Has this pointer | No this pointer |
| Defined in class | Must be defined outside class (usually in .cpp) |
this pointer, so static member functions cannot access non-static members. They exist independently of any object — you can call them even before any objects are created.static data member exist if you create 100 objects of the class?This chapter introduced the mechanics of classes: how the this pointer connects member functions to objects, how access specifiers enforce encapsulation, how constructors initialize objects, and how friends and static members extend the design.
| This Chapter | Builds Toward |
|---|---|
| Constructors | Copy constructors, move constructors (Ch 13) |
| Encapsulation | Inheritance and access control (Ch 15) |
| this pointer | Virtual functions and dynamic binding (Ch 15) |
| Member functions | Operator overloading (Ch 14) |
| static members | Class-level constants, singleton patterns |
Next up: Chapter 9: Sequential Containers — the library types that use classes to manage data for you.