-
Notifications
You must be signed in to change notification settings - Fork 0
Using Initializer Lists
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 regularUSB_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.
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.
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)
}
}- Required for non-default-constructible members
- More efficient (no temporary object or default + assignment)
- Required for const members and references
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
}
};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.
| 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 |
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.