Post

[C++] Class: Basic Syntax

[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

  1. Core Concepts of a Class
  2. class vs. struct (Basics)
  3. The Simplest Class: Declaration and Members
  4. Ensuring Initialization with Constructors
  5. Destructors and RAII
  6. Member Functions and const-correctness
  7. A More Practical Example: The Buffer Class
  8. Advanced Topics Preview

1. Core Concepts of a Class

  • Definition: A class is 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 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.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)

  • class and struct
    • There is no fundamental difference between a struct and a class; a struct is simply a class with members public by default.6

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 private data member value_ to hold an integer.
    • It provides a public interface with two member functions, set() and get(), to interact with the data.
    • Conventionally, we place the public declarations first and the private declarations later.8

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 new keyword. They persist until they are explicitly destroyed using the delete keyword.
  • They are accessed via pointers. Failure to call delete results 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 const or 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 the code_runner environment used here (gnu++17), only treat this as a warning (-Wnarrowing).
Therefore, the example uses #pragma to 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 the class.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 delete is 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 class are inlined by default.12
  • const Member Functions: A const member function can be invoked for both const and non-const objects, but a non-const member function can only be invoked for non-const objects.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 ▶
  • explicit Constructors: The explicit keyword prevents the compiler from using a constructor for implicit type conversions, which can help avoid surprising behavior.
  • const-Overloading: Providing two versions of at()—one const and one not—is a common C++ idiom. It allows const objects to get read-only access while non-const obje—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::vector member data_) 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

  1. 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

  2. Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 54. “The central language feature of C++ is the class.” ↩︎

  3. 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.” ↩︎

  4. 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.” ↩︎

  5. 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.” ↩︎

  6. 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.” ↩︎

  7. 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.” ↩︎

  8. Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 23. “…conventionally we place the public declarations first and the private declarations later…” ↩︎

  9. 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.” ↩︎

  10. 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.” ↩︎

  11. 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

  12. Bjarne Stroustrup, A Tour of C++ (3rd ed.), p. 56. “Functions defined in a class are inlined by default.” ↩︎ ↩︎2

This post is licensed under CC BY 4.0 by the author.