← C++, End to End

C++, End to End

More on Classes

Classes have more machinery than constructors and access specifiers. This page covers the rest of what you'll actually use: static members, const correctness on member functions, friends, delegating constructors, and the rule that decides whether you need to write your own copy/move/destructor logic at all.

Static members

A static data member belongs to the class itself, not to any one object. Every instance shares the same one. A static member function can be called without an object at all, and (because it isn't tied to an instance) has no this pointer, so it can only touch other static members.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

A static data member needs exactly one definition somewhere (traditionally a .cpp file), separate from its declaration in the class. static inline int count = 0; inside the class (C++17 and later) lets you skip the separate definition entirely, and is the easier default now.

Const member functions

Marking a member function const promises it won't modify the object it's called on. This isn't just documentation. The compiler enforces it: a const member function can't assign to member variables, and it's the only kind of member function you can call on a const object or through a const reference.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Get in the habit of marking every member function const unless it genuinely needs to mutate the object. It costs nothing, and it lets the rest of your code pass objects by const& freely, which is the default you want for anything you're not modifying.

Friend functions and classes

A friend declaration gives an outside function or class access to your private members, as if it were a member itself. It breaks encapsulation on purpose, for one specific relationship, and should be used sparingly: reach for it only when two things are so tightly coupled that a clean public interface between them would be more awkward than the access hole.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

The classic sign you're overusing friend: if you find yourself making half the codebase a friend of one class, that class's public interface is probably missing something it should expose properly instead.

Delegating constructors

A constructor can call another constructor of the same class in its initializer list, instead of duplicating setup logic. This is delegation, and it keeps constructors that differ only by defaults from repeating themselves.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Default member initializers

You can give a member variable a default value right where it's declared. Any constructor that doesn't explicitly initialize that member in its own list picks up the default automatically. This removes a whole class of "forgot to initialize a field in one of three constructors" bugs.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

The rule of zero, three, and five

If a class manages a resource directly (raw memory via new, a file handle, a socket), the compiler-generated copy constructor, copy assignment operator, and destructor will do the wrong thing: they copy the pointer, not what it points to. Two objects end up owning the same resource, and when both destructors run, you get a double free.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Don't run the example above for real, it's a genuine double free. The point is to see why the bug exists, not to trigger it.

The fix is either the rule of three (write a copy constructor, copy assignment operator, and destructor together, since needing one strongly implies needing the other two) or, better, the rule of zero: don't manage the raw resource yourself at all. Wrap it in a type that already does this correctly, like std::string, std::vector, or a smart pointer, and let your class's compiler-generated special members work by simply calling that member's own correct versions.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Rule of five extends rule of three to also cover move constructor and move assignment, which matter once you care about avoiding unnecessary copies (the dedicated move semantics chapter elsewhere in this course covers that in depth). The practical takeaway is the same either way: reach for rule of zero by default, and only write the special members yourself when your class is the one place directly responsible for owning a raw resource.

Try it yourself: spot the missing rule-of-three piece

This class manages a raw array itself, and writes a destructor and copy constructor, but is still broken. Find the missing piece, and explain what goes wrong without it.

c++
class IntArray {
    int* data;
    int size;
public:
    IntArray(int n) : size(n) { data = new int[n]{}; }
    ~IntArray() { delete[] data; }

    IntArray(const IntArray& other) : size(other.size) {
        data = new int[size];
        for (int i = 0; i < size; ++i) data[i] = other.data[i];
    }
    // No copy assignment operator (operator=) written.
};

Without a copy assignment operator, IntArray a(5); IntArray b(3); b = a; uses the compiler-generated one, which just copies the data pointer field, not the array. Now a and b share one buffer, and both destructors will eventually delete[] it. If you write the copy constructor, you almost always need the copy assignment operator too, that's the actual content of "rule of three": these three members come as a package, not a menu.