[C++] Class: Basic Syntax
Abstract
As a core User-Defined Type (UDT) in C++, the class is fundamental for object-oriented programming. It encapsulates data and behavior, separating the public interface from the private implementation1, while managing resource lifetime through constructors and destructors (RAII).
This article provides a practical guide to the essential syntax, covering class declaration, access control (class vs. struct), member initialization, const-correctness, and the role of constructors and destructors in object lifecycle management.
The central language feature of C++ is the
class.2
Introduction
This article aims to equip readers with the ability to use C++ classes effectively, focusing on the minimal syntax required without compromising safety or established idioms.
The guiding principle is to distinguish between the interface to a type (to be used by all) and its implementation (which has access to the otherwise inaccessible data)1. Hiding internal implementation details is a cornerstone of robust object-oriented design.
Outline
- Core Concepts of a Class
- class vs. struct (Basics)
- The Simplest Class: Declaration and Members
- Ensuring Initialization with Constructors
- Destructors and RAII
- Member Functions and const-correctness
- A More Practical Example: The Buffer Class
- Advanced Topics Preview
1. Core Concepts of a Class
- Definition: A
classis a user-defined type provided to represent an entity in the code of a program.3 - Core Philosophy: Whenever our design for a program has a useful idea, entity, etc., we try to represent it as a
classin the program so that the idea is there in the code, rather than just in our heads, in a design document, or in some comments.4 - Concrete Type: The defining characteristic of a concrete type is that its representation is part of its definition.5
2. class vs. struct (Basics)
classandstruct- There is no fundamental difference between a
structand aclass; astructis simply aclasswith memberspublicby default.6
- There is no fundamental difference between a
3. The Simplest Class: Declaration and Members
A class bundles data and behavior. The interface of a class is defined by its public members, and its private members are accessible only through that interface.7
Here is a simple class declaration:
1
2
3
4
5
6
7
8
class SimpleClass {
public:
void set(int v) { value_ = v; } // Public member function (setter)
int get() { return value_; } // Public member function (getter)
private:
int value_; // Private data member
};
- Key Points
- We define a new type named
SimpleClass. - It has a
privatedata membervalue_to hold an integer. - It provides a
publicinterface with two member functions,set()andget(), to interact with the data. - Conventionally, we place the
publicdeclarations first and theprivatedeclarations later.8
- We define a new type named
4. Ensuring Initialization with Constructors
A member function with the same name as its class is called a constructor. Unlike an ordinary function, a constructor is guaranteed to be used to initialize objects of its class. Thus, defining a constructor eliminates the problem of uninitialized variables for a class.9
We can improve SimpleClass by adding a constructor to ensure that objects are always initialized with valid values.
1
2
3
4
5
6
7
8
9
10
11
12
class SimpleClass {
public:
// Constructor
SimpleClass(int v) {
value_ = v; // Initialization via assignment
}
int get() const { return value_; }
private:
int value_;
};
4.1. Class Instantiation
A class with a defined constructor can be instantiated (i.e., an object can be created) in the following ways:
Click Run ▶
The lifetime of an object depends on its storage duration. The two primary kinds are:
1. Automatic Storage (Stack)
SimpleClass s1(5);- Objects with automatic storage duration are declared within a block (like a function). They are automatically created at the point of declaration and destroyed when the block is exited. This is the foundation of RAII.
- Direct Initialization (
s1(5)): The traditional method of calling the constructor directly. - List Initialization (
s2{10},s3 = {15}): A safer and more consistent initialization syntax introduced in C++11.
2. Dynamic Storage (Heap)
SimpleClass* p1 = new SimpleClass(20);- Objects with dynamic storage duration are created using the
newkeyword. They persist until they are explicitly destroyed using thedeletekeyword. - They are accessed via pointers. Failure to call
deleteresults in a memory leak.
4.2. Member Initializer Lists
In the previous example’s constructor, we initialized the member via assignment, like value_ = v;. However, using a member initializer list is a more efficient and recommended approach in C++.
- Initialization vs. Assignment: An initializer list initializes member variables when they are created. In contrast, code in the constructor body assigns values to already-created member variables. Members that are
constor references (&) must be initialized when they are created, so they must use an initializer list.
1
2
3
4
5
6
7
8
9
10
class SimpleClass {
public:
// Constructor using a member initializer list
SimpleClass(int v) : value_{v} {
// The constructor body can now be empty.
}
// ...
private:
int value_;
};
4.3. () vs. {} Initialization: Preventing Narrowing Conversions
In an initializer list, you can use both () and {}. However, the {} (list initialization) style, introduced in C++11, is safer. The primary reason is that it prohibits “narrowing conversions,” which can cause data loss, at compile time.
Click Run ▶
In the code above, the int value 1000 is outside the range that a char can represent, so this is a narrowing conversion. Constructor (2), which uses {}, treats this as a compile error, preventing the bug in advance.
Note:
Narrowing conversions are defined as errors by the C++11 standard.
However, many compiler settings, including thecode_runnerenvironment used here (gnu++17), only treat this as a warning (-Wnarrowing).
Therefore, the example uses#pragmato elevate this specific warning to an error.
5. Destructors and RAII
The complement to a constructor is a destructor. A destructor is a special member function that is called automatically when an object’s lifetime ends.
Its purpose is to perform cleanup tasks, such as releasing resources that the object acquired during its lifetime.
- Syntax: The name of a destructor is the
~followed by the name of theclass.10 It takes no arguments and has no return type. - Automatic Invocation: A destructor is automatically invoked when an object goes out of scope or when
deleteis called on a pointer to an object. - RAII: The technique of acquiring resources in a constructor and releasing them in a destructor, known as Resource Acquisition Is Initialization or RAII.11 Here is an example demonstrating the destructor’s lifecycle:
5.1. Destructor Lifecycle
Click Run ▶
When this code is run, you will see the constructor and destructor messages interleaved, clearly showing when each object is created and destroyed. The destructor for stackObj is called automatically at the end of main.
5.2. RAII in Practice
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <cstdio>
class FileGuard {
public:
FileGuard(std::FILE* f) : f_(f) {}
~FileGuard() {
if (f_) std::fclose(f_);
} // Resource released in destructor (RAII)
std::FILE* get() const { return f_; }
private:
std::FILE* f_{};
};
When a FileGuard object goes out of scope, its destructor is automatically called, ensuring the file is closed.
6. Member Functions and const-correctness
- Member Functions: Functions defined in a
classare inlined by default.12 constMember Functions: Aconstmember function can be invoked for bothconstandnon-constobjects, but a non-constmember function can only be invoked fornon-constobjects.11
The get() function in our SimpleClass class doesn’t (and shouldn’t) modify the object’s state. We can and should mark it as const.
1
2
3
4
5
6
7
8
9
10
class SimpleClass {
public:
SimpleClass(int v) : value_{v} {}
void set(int v) { value_ = v; }
int get() const { return value_; } // This function does not modify the object
private:
int value_;
};
Marking getters as const is a fundamental part of writing correct and robust C++ code.
7. A More Practical Example: The Buffer Class
Now that we understand the basics, let’s look at a more realistic example that combines these concepts.
Click Run ▶
explicitConstructors: Theexplicitkeyword prevents the compiler from using a constructor for implicit type conversions, which can help avoid surprising behavior.const-Overloading: Providing two versions ofat()—oneconstand one not—is a common C++ idiom. It allowsconstobjects to get read-only access while non-constobje—ts can get read—write access.- Handles: A common technique for handling varying amounts of information is to use a fixed-size handle (like the
std::vectormemberdata_) referring to a variable amount of data “elsewhere”12.
8. Advanced Topics Preview
- Operator Overloading (member/non-member, symmetric operation idioms)
- Copy/Move,
=default/=delete, Rule of 0/3/5 - Inheritance, Virtual Destructors, Polymorphism, Interface Segregation
explicit, Conversion Constructors, Delegating/Inheriting Constructors,friend
References
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 23. “…distinguish between the interface to a type (to be used by all) and its implementation (which has access to the otherwise inaccessible data).” ↩︎ ↩︎2
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 54. “The central language feature of C++ is the class.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 54. “A class is a user-defined type provided to represent an entity in the code of a program.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 54. “Whenever our design for a program has a useful idea, entity, collection of data, etc., we try to represent it as a class in the program so that the idea is there in the code, rather than just in our heads, in a design document, or in some comments.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 54. “The defining characteristic of a concrete type is that its representation is part of its definition.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 25. “There is no fundamental difference between a struct and a class; a struct is simply a class with members public by default.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 23. “The interface of a class is defined by its public members, and its private members are accessible only through that interface.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 23. “…conventionally we place the public declarations first and the private declarations later…” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 24. “A member function with the same name as its class is called a constructor… Unlike an ordinary function, a constructor is guaranteed to be used to initialize objects of its class. Thus, defining a constructor eliminates the problem of uninitialized variables for a class.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 57. “The name of a destructor is the complement operator, ~, followed by the name of the class; it is the complement of a constructor.” ↩︎
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 58. “The technique of acquiring resources in a constructor and releasing them in a destructor, known as Resource Acquisition Is Initialization or RAII.” ↩︎ ↩︎2
Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 56. “Functions defined in a
classare inlined by default.” ↩︎ ↩︎2