Uniform Initialization in C++: The Power of Braces

When writing C++, do you often find yourself struggling with whether to use <span>=</span> or <span>()</span> to initialize variables?

Sometimes you write it as <span>int a = 10;</span>

Other times you write it as <span>std::string s("hi");</span>

Can class member variables only be initialized through constructors? 🤯

To address the above issues, C++11 introduced a powerful feature: Uniform Initialization.

In simple terms, you can use a pair of braces <span>{}</span> to uniformly initialize objects.

Today, we will introduce this feature.

Braces Can Initialize Everything ✨

Whether it’s built-in types, arrays, or standard containers, you can use <span>{}</span> to initialize them:

int a{10};                 // Built-in type
std::string s{"hello"};    // Class type
int arr[3]{1, 2, 3};       // Array
std::vector v{1, 2, 3, 4}; // Container

What used to be a variety of initialization methods can now be uniformly done with braces, making it easier ✅.

Safer: Avoid Narrowing Conversions 🚫

You may have written code like this:

int x = 3.5;   // Compiles, but x = 3, losing the decimal part

This is known as narrowing conversion, where data is forcibly crammed into a smaller type, resulting in a silent loss of precision.

If you switch to using <span>{}</span>:

int x{3.5};    // ❌ Compilation error, directly prevented

Thus, uniform initialization helps us maintain type safety.

More Elegant Member Initialization 🏗️

Previously, we often had to assign values to members in the constructor; now we can directly use braces at the time of definition, making the code look cleaner and safer:

class Point {
  public:
    int x{0};
    int y{0};
  public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
};

Point p1;        // x = 0, y = 0
Point p2{3, 4};  // x = 3, y = 4

The benefits of this approach are:

  • Each member has a clear default value at declaration
  • The constructor only needs to focus on differentiated assignments
  • Reduces bugs caused by forgotten initializations

The “Pitfall” of initializer_list ⚠️

Here’s a common “pitfall” to be particularly aware of:If a class defines a <span>std::initializer_list</span> constructor, then <span>{}</span> will prioritize matching it.

class Foo {
  public:
    Foo(int x, double y) {
        std::cout << "int, double" << std::endl;
    }

    Foo(std::initializer_list) {
        std::cout << "initializer_list" << std::endl;
    }
};

Foo f1{1, 2};   // Outputs "initializer_list"
Foo f2(1, 2);   // Outputs "int, double"

👉 Therefore, if you only want to use the regular constructor but the class has an <span>initializer_list</span> constructor, then use <span>()</span>.

Conclusion 💡

Uniform initialization is a small change introduced in C++11, but it greatly enhances the consistency, safety, and readability of the code.

If you have been hesitating between using <span>=</span> and <span>()</span>, then from now on, consider using <span>{}</span> more often — braces are the way to go 🚀.

Leave a Comment