Skip to content

Using Initializer Lists

AJ but at Work edited this page Aug 5, 2025 · 1 revision

C++ Lesson: Constructor Initializer Lists (with Member Class Initialization)

Question: Why do I have to use USB_Serial _asa{"/dev/ttyUSB0", B115200}; when creating an instance of my class within a class instead of my regular USB_Serial _asa("/dev/ttyUSB0", B115200); when creating an instance from main.cpp

In main.cpp, you're writing something like: USB_Serial _asa("/dev/ttyUSB0", B115200);

This is a constructor call, creating _asa and initializing it with those arguments - perfect! But inside another class, you can't just do that directly in the class body like this:

class MyClass 
{
  USB_Serial _asa("/dev/ttyUSB0", B115200); // ❌ not allowed!
};

That is because inside a class definition, you declaring members, not calling constructors.

In this lesson, we’ll walk through how to use constructor initializer lists in C++ — especially when a class contains another class as a member.

Background

When a class contains another class as a member variable, that member must be initialized when the parent object is created. When you're initializing a member that requires constructor parameters (like USB_Serial here), you must use a member initializer list in the constructor of your containing class. The best way to do this is with a constructor initializer list.

Basic Syntax

class Inner 
{
  public:
    Inner(int value) { /* ... */ }
};

Now, if we have another class that has an Inner as a member:

class Outer 
{
  Inner inner; // Private member variable. Anyone accessing Outer doesn't need to access inner

  public:
      // Constructor uses initializer list to initialize 'inner'
      Outer() : inner(42) 
        {
            // constructor body (optional setup logic)
        }
};

First we must create a member variable of the class Inner, like this; Inner inner;

Then we use an initializer list to pass in the arguments that it requires. Outer() : inner(420)

You can modify the Outer class to accept those arguments, so they are configured at runtime. You do that by doing this:

class Outer
{
  Inner inner; // private member variable

  public:
    Outer(int value) : inner(value)
      {
        // constructor body (optional setup logic)
      }

}

Why use initializer lists?

  1. Required for non-default-constructible members
  2. More efficient (no temporary object or default + assignment)
  3. Required for const members and references

Real Example: Serial Wrapper

class USB_Serial {
public:
    USB_Serial(const std::string& portName, speed_t baudRate);
    bool init();
    void send(const std::string& data);
};

And we want to build a class Jbus that wraps this:

class Jbus
{
  USB_Serial serialPort;

  public:
    // Constructor with an initializer list
    Jbus(const std::string& port, speed_t baud)
        : serialPort(port, baud) {/* * */} // ✅ Constructed here!

    // example command
    void sendCommand(const std::string& cmd) // You can access this via Jbus
      {
        serialPort.send(cmd); // serialPort remains private to Jbus
      }
};

Alternative: Overloaded Constructors

class Jbus 
{
    USB_Serial serialPort;

  public:
    Jbus() : serialPort("/dev/ttyASA", B115200) {/* * */}

    Jbus(const std::string& port, speed_t baud)
        : serialPort(port, baud) {/* * */}
};

Now you can either hardcode or pass it in dynamically.


Summary

Concept Rule
Class contains another class Must initialize it in the parent’s constructor
Want to pass arguments Use initializer list
const or no-default-constructor Initializer list is required
Cleaner code Prefer initializer list even when optional

✅ Pro Tip

Order matters! Members are initialized in the order they are declared in the class, not the order in the initializer list.

class Foo {
    Bar b;
    Baz z;

    Foo() : z(), b() {} // 🧨 Bad: b initialized before z
};

Compiler will warn you.

Clone this wiki locally