Skip to content
VLSI Mentor

C++ Part I · Module 11 · Classes and Encapsulation

Defining a Class and Creating Objects

The previous chapter presented a working class whole, with its constructor and its private section and its accessors arriving together, because the goal there was to show that the idea works. That is the wrong way to learn to write one. This chapter starts from nothing and adds the pieces in order, and it is deliberately mechanical — the ideas were the last chapter's, and the only thing left is the syntax plus three specific points where that syntax surprises people. The first is the semicolon after the closing brace, which is required for a reason worth knowing rather than memorising, and whose absence produces a clear message in a small file and a cascade of unrelated nonsense in a header. The second is the difference between telling the compiler that a type exists and telling it what the type contains: the first is enough to declare a pointer to it and not enough to create one or even ask its size, and the measured error messages mark that boundary precisely. The third is the distinction that causes more early confusion than anything else in the module — between the class, which you write once and which occupies no storage at all, and the objects of it, each of which has its own copy of every member and its own address. That one is easy to state and easy to half-believe, so it is measured here instead: three objects of one type, given different values, with their addresses printed.

Foundation16 min readC++Part IFundamentalsClasses

Part I · Module 11 · Chapter 11.2 · Defining a Class and Creating Objects

1. Starting From Nothing

Chapter 11.1 §5 showed a finished Field. Here is the same thing assembled a piece at a time, so that each piece has a reason.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Counter {
    };

That is a complete, legal class with no members. It is useless and it compiles. Everything from here is adding to it.

2. The Form

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Counter {
    public:
        void bump()        { ++n_; }
        int  value() const { return n_; }
    private:
        int n_ = 0;
    };          // <-- this semicolon is required

Five things are present, and three of them belong to later chapters:

class Counter — the keyword and the type's name. The name becomes a type you can use exactly like int.

The braces — everything between them is the class's body.

public: and private: — access specifiers. Chapter 11.4 is what they mean; for now, public is reachable from outside and private is not.

void bump() and int value() const — member functions. Chapter 11.3 is where they belong and how to write them; the const on the second is Chapter 11.7's subject.

int n_ = 0; — a data member with a default member initializer. The = 0 means an object of this type starts with n_ equal to zero without any constructor being written, which is why this chapter can defer Chapter 12.2 entirely.

(The trailing underscore on n_ is a convention, not a rule. It distinguishes members from parameters and locals, which matters in Chapter 11.6 §5.)

3. The Semicolon

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
semi.cpp:1:39: error: expected ';' after class

A clear message, and in a small file the fix is obvious. The reason it is worth a section is that the semicolon exists because a class definition can declare objects at the same time:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Counter { /* ... */ } a, b;      // legal: defines the type AND two objects

That form is rarely useful and it is why the grammar needs a terminator. The practical consequence is the failure mode: omit the semicolon at the end of a class in a header, and the compiler tries to read whatever follows the } as the names of objects being declared. The errors then appear on the next declaration, or in a completely different file that includes the header, and they do not mention the semicolon at all. Chapter 2.7 §4's advice applies exactly — read the first error, and when it makes no sense, look at the line before.

4. Declaring Versus Defining

Two different statements about a type, and the difference is measurable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Field;                 // DECLARATION: "a type called Field exists"
    class Field { int a, b; };   // DEFINITION:  "and here is what it contains"

A declaration alone makes the type incomplete. Measured, what that permits and forbids:

With only class Field;Result
Field* p = nullptr;OK — a pointer to an incomplete type is fine
Field f;error: variable has incomplete type 'Field'
note: forward declaration of 'Field'
sizeof(Field)error: invalid application of 'sizeof' to an incomplete type 'Field'

The pattern behind those three rows: you may refer to an incomplete type, but you may not do anything that requires knowing its size. Creating an object requires a size. sizeof obviously does. A pointer does not, because Chapter 8.1 measured every pointer as 8 bytes regardless of what it points at.

That is a genuinely useful tool rather than a technicality. Two types that refer to each other need it, and a header that only mentions a type in a pointer or reference can forward-declare it instead of including its definition — which is the main lever for keeping compile times down in a large codebase, since Chapter 2.5 established that an #include is a literal textual insertion.

Where the definition goes. A class definition normally lives in a header, because every file that creates objects of the type needs to know its size. The definition may appear in each translation unit that includes it — Chapter 2.6's subject — but must be identical in all of them, which is the One-Definition Rule, named here and properly treated in Chapter 24.1. In practice that is what include guards are for (Chapter 2.5 §6), and it is why you should never write a class definition in two places by hand.

5. Creating Objects

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Counter a;                 // one object, default-initialized members
    Counter arr[4];            // four objects
    Counter* p = new Counter;  // one object with dynamic storage duration

The type name is used exactly like a built-in type name, which is the point: Counter a; has the same shape as int a;. Everything Module 7 established about storage duration applies unchanged — Chapter 7.3's four durations, and Chapter 10.5's measured trace of when each begins and ends.

So an object of a class type can live in any of the places an int can:

Automatic — Counter a; inside a function. Ends at the closing brace.

Static — at namespace scope or declared static. Chapter 7.4, and Chapter 10.5 measured one being constructed before main and destroyed after it.

Dynamic — new Counter, Chapter 10.3, with all of Module 10's consequences and Chapter 10.9's question.

And as a member of another object, which is how most real composition happens.

Prefer automatic, for every reason Chapter 10.1 §7 gave.

6. The Class Occupies No Storage

A point that sounds pedantic and prevents a real confusion:

The class is a description. The objects are the things that exist.

Writing class Counter { int n_; }; allocates nothing and produces no object. It tells the compiler what a Counter is — how large, what members at what offsets, what operations are permitted. Chapter 9.8 §5's layout rules are exactly this description.

Measured, for the Counter above: sizeof(Counter) is 4 — the size of one object, which is the int and nothing else. There is no per-type overhead stored anywhere, and Chapter 11.1 §4 measured that ten member functions add nothing either.

7. Objects Are Independent — Measured

The claim everyone nods at and some people do not quite believe.

Azvya Education Pvt. Ltd.VLSI Mentor
instances.cpp — complete program
#include <cstdio>

class Counter {
public:
    void bump()        { ++n_; }
    int  value() const { return n_; }
private:
    int n_ = 0;
};

int main()
{
    Counter a, b, c;
    a.bump(); a.bump(); a.bump();
    b.bump();
    std::printf("a=%d b=%d c=%d\n", a.value(), b.value(), c.value());
    std::printf("addresses: a=%p b=%p c=%p\n",
                (void*)&a, (void*)&b, (void*)&c);
    std::printf("all distinct? %s\n",
                (&a != &b && &b != &c && &a != &c) ? "yes" : "no");

    Counter arr[4];
    arr[2].bump();
    std::printf("array of 4: values %d %d %d %d   stride %td\n",
                arr[0].value(), arr[1].value(), arr[2].value(), arr[3].value(),
                (char*)&arr[1] - (char*)&arr[0]);
    std::printf("sizeof(Counter) = %zu\n", sizeof(Counter));
    return 0;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a=3 b=1 c=0
addresses: a=0x16dc02740 b=0x16dc0273c c=0x16dc02738
all distinct? yes
array of 4: values 0 0 1 0   stride 4
sizeof(Counter) = 4

Four things read off it.

Each object has its own n_. Three bumps on a, one on b, none on c, and the values are 3, 1, 0. The member function bump() is one function; the data it changes depends on which object it was called on, which is Chapter 11.6's subject.

Each object has its own address, and they are distinct. (They descend — …740, …73c, …738 — because these are automatic objects and Chapter 10.2 measured the stack growing downward. That is an ABI detail, not a rule.)

The array line is the better evidence. Bumping arr[2] left the other three at zero. Four objects of one type, one of them modified, and nothing leaked across.

And the stride is 4, matching sizeof(Counter) — so an array of class objects is laid out exactly as Chapter 9.1 measured for int: contiguous, one after another, no per-object overhead.

8. The Two Vocabularies

Worth having straight, because the words get used loosely and the distinction is the chapter's purpose:

The typeThe objects
Countera, b, c, arr[0]…
written oncecreated many times
occupies no storageeach occupies sizeof(Counter)
has members declaredhas members existing, with values
has member functionsmember functions are called on them
sizeof(Counter)&a, a.value()

The term for one object of a class type is an instance of that class. And the one phrase to be careful with is "the class's data" — a class declares what data each of its objects will have; it does not have data itself. The exception is a static data member, which genuinely belongs to the type rather than to any object, and that is Chapter 11.8.

9. What to Actually Do

Put the class definition in a header, guarded, so every user sees the same one (Chapter 2.5 §6).

Forward-declare when you only need a pointer or reference. It is the main tool against long compile times.

Remember the semicolon, and when a header produces nonsense errors, check the line before the first one.

Give every data member a default member initializer unless a constructor will set it.

Use the type name exactly like a built-in one — Counter a; is int a; in shape, and storage duration works identically.

Prefer automatic storage for objects, per Chapter 10.1 §7.

Keep "the type" and "an object of the type" separate in how you speak. The class has no data; its objects do.

And use a trailing underscore or similar for members. It pays off in Chapter 11.6.

10. Mistakes Worth Making Once

Omitting the semicolon after the closing brace. Clear in a small file, baffling in a header.

Creating an object of a forward-declared type. error: variable has incomplete type.

Calling sizeof on an incomplete type. Also an error, for the same reason.

Writing the class definition in two places instead of one header. The One-Definition Rule, Chapter 24.1.

Expecting the class itself to hold data. It is a description; static members are the one exception (Chapter 11.8).

Assuming objects share members. Measured: bumping arr[2] left the other three at 0.

Leaving members without initializers and no constructor. Chapter 3.8's bug, and Chapter 10.3 §5 measured that the dynamic version draws no warning.

Writing a class with private data and no invariant and expecting a benefit. That is a struct with extra typing.

11. Interview Relevance

"What is the difference between a class and an object?" The class is a description written once, occupying no storage; an object is an instance of it with its own members and address. Basic, and often answered vaguely.

"What does a forward declaration let you do?" Refer to the type — pointers and references — but not create objects or take sizeof, because those need the size. The follow-up worth volunteering is that this is how you cut include dependencies.

"Where should a class definition live?" A guarded header, with one definition seen identically by every translation unit.

"Do objects of the same class share their data members?" No — one copy per object. A static data member is the exception and belongs to the type.

"Why the semicolon?" Because a class definition can declare objects after the closing brace. Knowing why rather than just that is the distinguishing detail.

12. What You Should Be Able to Say Now

  1. A class body between braces declares members, and the definition ends with a required semicolon.
  2. The semicolon exists because a definition can declare objects after the brace — hence the confusing errors when it is missing.
  3. class Field; is a declaration; with a body it is a definition.
  4. Measured: with only a declaration, Field* p is OK, Field f; is incomplete type, and sizeof is invalid application.
  5. The rule behind those: you may refer to an incomplete type but not do anything needing its size.
  6. Forward declaration is the tool for cutting include dependencies, since #include is textual (Chapter 2.5).
  7. The definition belongs in a guarded header, identical everywhere — the One-Definition Rule (Chapter 24.1).
  8. Objects can have automatic, static or dynamic storage duration, or be members of other objects.
  9. The class occupies no storage — it is a description; sizeof(Counter) was 4, the size of one object.
  10. Measured: three objects gave 3, 1, 0 with distinct addresses.
  11. Measured: bumping arr[2] left the other three at 0, and the array stride was 4 — contiguous, no overhead.
  12. The word for one object of a class type is an instance.
  13. A default member initializer initializes but cannot validate — refusing a value needs a constructor (Chapter 12.2).

13. Exercises

Exercise 1 — assemble it. Write a class Latch with a private bool member defaulting to false, a set() that makes it true, and a bool state() const. Include the semicolon.

Exercise 2 — predict. With only class Widget; in scope, say which compile: (a) Widget* p; (b) Widget w; (c) Widget& r = *p; (d) sizeof(Widget) (e) void f(Widget*); (f) void g(Widget);

Exercise 3 — the semicolon. Explain why omitting it in a header produces errors in a different file, and what grammar rule is responsible.

Exercise 4 — independence. Predict the output for four Counter objects where only the third is bumped twice, and say what the stride should be.

Exercise 5 — where does it live? For each, name the storage duration and when the object ends: (a) Counter c; in a function (b) Counter c; at namespace scope (c) new Counter (d) a Counter member of another class.

Exercise 6 — cut the include. A header declares void process(Buffer* b);. Say whether it needs #include "buffer.h" and why.

Exercise 7 — the gap. Counter has private data and no invariant. Say what it has gained over a struct, what it has not, and which chapter closes the difference.

14. Summary

  • A class body declares members and ends with a required semicolon, which exists because objects may be declared after the brace.
  • Declaration versus definition: class Field; makes the type incomplete.
  • Measured: a pointer to it is OK; an object is incomplete type; sizeof is an error.
  • You may refer to an incomplete type but not need its size — and that is the lever for cutting include dependencies.
  • The definition belongs in one guarded header, identical in every translation unit.
  • Objects take any storage duration — prefer automatic.
  • The class occupies no storage; it is a description. sizeof(Counter) was 4.
  • Measured: three objects held 3, 1 and 0 at distinct addresses; bumping arr[2] left the others at 0; stride 4.
  • One object of a class type is an instance.
  • Default member initializers initialize but cannot validate — that needs Chapter 12.2's constructor.

A type and its objects are now separable, and the classes written so far are nearly empty: a member or two, and the two functions needed to make the measurement work. Those functions were used without being explained, including one that carried a const nobody accounted for. The next chapter is about them properly — where a member function may be defined, why defining it inside the class body is not the same as defining it outside even though both are legal, how it reaches the object's data without being handed a parameter, and the question that turns out to be the actual design work rather than the syntax: given an operation that involves a type, deciding whether it belongs inside the type at all. There is a measurement for the mechanism too, and it is a surprising one — the pointer to a member function turned out to be four times the size of the object.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.