C++ Uniform Initialization | Braces `{}` Explained
이 글의 핵심
Uniform initialization lets you initialize any type with `{}` for consistent syntax, narrowing checks, and clearer object construction than parentheses alone.
What is uniform initialization?
Uniform initialization (C++11) is the consistent {} syntax for initializing values of any type: scalars, arrays, structs, classes, and containers.
// Before C++03: many styles
int x = 10;
int arr[] = {1, 2, 3};
std::vector<int> vec;
vec.push_back(1);
// C++11: uniform initialization
int x{10};
int arr[]{1, 2, 3};
std::vector<int> vec{1, 2, 3};
Why it matters:
- Consistency: one syntax for many types
- Safety: catches narrowing conversions
- Clarity: avoids the Most Vexing Parse
- Convenience: simpler container initialization
Comparison of styles:
| Style | Syntax | Narrowing | Most Vexing Parse |
|---|---|---|---|
| Copy initialization | int x = 10; | Allowed (often with warning) | — |
| Direct initialization | int x(10); | Allowed | Can bite |
| Brace / uniform | int x{10}; | Usually rejected | Avoided with {} |
Basic usage
// Scalars
int x{10};
double y{3.14};
char c{'A'};
// Arrays
int arr[]{1, 2, 3, 4, 5};
// Struct
struct Point {
int x, y;
};
Point p{10, 20};
// Class types
std::string s{"Hello"};
std::vector<int> vec{1, 2, 3};
Narrowing prevention
// ❌ Narrowing error
int x{3.14}; // compile error: double -> int
// ✅ Explicit conversion
int x{static_cast<int>(3.14)};
// Copy init may allow (with warnings)
int y = 3.14; // OK (warning)
Narrowing in more detail:
Brace initialization often rejects these lossy conversions:
// ❌ Floating -> integer
double d = 3.14;
int x{d}; // error
// ❌ Large integer -> smaller
long long big = 1000000000000LL;
int y{big}; // error
// ❌ Integer -> float (precision loss)
int large = 16777217;
float f{large}; // error (not exactly representable as float)
// ❌ Signed -> unsigned (negative value)
int negative = -1;
unsigned int u{negative}; // error
// ✅ Explicit casts
int x2{static_cast<int>(d)};
int y2{static_cast<int>(big)};
float f2{static_cast<float>(large)};
unsigned int u2{static_cast<unsigned int>(negative)};
Practical use:
// ✅ Safer API parameters
void setVolume(int volume) {
// volume: 0~100
}
double userInput = 75.5;
// setVolume({userInput}); // compile error (narrowing)
setVolume(static_cast<int>(userInput)); // explicit intent
// ✅ Parsing config values
int parsePort(const std::string& value) {
double parsed = std::stod(value);
// return {parsed}; // error: double -> int
return static_cast<int>(parsed);
}
Fixing the Most Vexing Parse
class Widget {
public:
Widget() {}
};
// ❌ Most Vexing Parse
Widget w(); // parsed as a function declaration!
// ✅ Brace initialization
Widget w{}; // object construction
What is the Most Vexing Parse?
Because () can mean both function declaration and construction, the compiler may interpret T x(); as declaring a function.
class Timer {
public:
Timer() { std::cout << "Timer constructed\n"; }
};
int main() {
Timer t(); // ❌ function declaration: Timer t();
// t.start(); // error: t is a function
Timer t2{}; // ✅ object
}
A subtler example:
#include <fstream>
#include <iterator>
#include <algorithm>
int main() {
std::ifstream file("data.txt");
// ❌ Most Vexing Parse
std::vector<int> data(
std::istream_iterator<int>(file),
std::istream_iterator<int>()
);
// `data` is parsed as a function declaration!
// ✅ Fix 1: braces
std::vector<int> data{
std::istream_iterator<int>(file),
std::istream_iterator<int>()
};
// ✅ Fix 2: extra parentheses
std::vector<int> data2(
(std::istream_iterator<int>(file)),
(std::istream_iterator<int>())
);
}
Real-world examples
Example 1: containers
#include <vector>
#include <map>
#include <set>
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5};
std::map<std::string, int> ages{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}
};
std::set<int> uniqueNumbers{5, 2, 8, 1, 9};
std::vector<std::vector<int>> matrix{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
}
Example 2: structs and classes
struct Person {
std::string name;
int age;
double height;
};
class Rectangle {
private:
int width, height;
public:
Rectangle(int w, int h) : width{w}, height{h} {}
int area() const {
return width * height;
}
};
int main() {
Person p{"Alice", 30, 165.5};
Rectangle rect{10, 20};
std::cout << rect.area() << std::endl; // 200
}
Example 3: dynamic allocation
#include <memory>
int main() {
int* ptr = new int{42};
delete ptr;
int* arr = new int[5]{1, 2, 3, 4, 5};
delete[] arr;
auto unique = std::make_unique<int>(42);
auto shared = std::make_shared<std::string>("Hello");
auto vec = std::make_unique<std::vector<int>>(
std::initializer_list<int>{1, 2, 3, 4, 5}
);
}
Example 4: return values
#include <vector>
std::vector<int> getNumbers() {
return {1, 2, 3, 4, 5};
}
struct Point {
int x, y;
};
Point getOrigin() {
return {0, 0};
}
int main() {
auto numbers = getNumbers();
auto origin = getOrigin();
}
= vs {}
// Copy-style init
int x = 10;
int y = 3.14; // narrowing allowed (warning)
// Braces
int a{10};
// int b{3.14}; // error: narrowing
std::string s1 = "Hello"; // OK
std::string s2{"Hello"}; // OK
// Direct vs brace for containers
std::vector<int> v1(10); // 10 elements (default value)
std::vector<int> v2{10}; // 1 element with value 10
initializer_list precedence
class MyClass {
public:
MyClass(int x) {
std::cout << "int ctor\n";
}
MyClass(std::initializer_list<int> list) {
std::cout << "initializer_list ctor\n";
}
};
int main() {
MyClass obj1(10); // int ctor
MyClass obj2{10}; // initializer_list ctor wins!
}
Common pitfalls
Pitfall 1: vector size vs element list
// ❌ Not what people sometimes expect
std::vector<int> v1(10); // 10 elements (zero-initialized)
std::vector<int> v2{10}; // 1 element: 10
// ✅ Be explicit
std::vector<int> v3(10, 0); // 10 elements, all 0
std::vector<int> v4{1, 2, 3}; // 3 elements
Pitfall 2: with auto
// ❌ Surprising type
auto x = {1, 2, 3}; // std::initializer_list<int>
// ✅ Name the container
std::vector<int> y = {1, 2, 3};
Pitfall 3: narrowing
int x{3.14}; // error
char c{1000}; // error (typically)
int x{static_cast<int>(3.14)};
char c{static_cast<char>(1000)};
Pitfall 4: empty braces
class MyClass {
public:
MyClass() {
std::cout << "default ctor\n";
}
MyClass(std::initializer_list<int> list) {
std::cout << "initializer_list ctor\n";
}
};
int main() {
MyClass obj1; // default ctor
MyClass obj2{}; // default ctor
MyClass obj3{{}}; // initializer_list ctor (empty list)
}
Member initialization
class MyClass {
private:
int x{10};
std::string s{"Hello"};
std::vector<int> vec{1, 2, 3};
public:
MyClass() = default;
MyClass(int value) : x{value} {}
};
Aggregate-style initialization
struct Point {
int x, y, z;
};
Point p1{1, 2, 3};
Point p2{1, 2}; // z value-initialized
Point p3{}; // all zero / value-init
struct Line {
Point start, end;
};
Line line{{0, 0, 0}, {10, 10, 10}};
Pros and cons
// ✅ Pros
int x{10};
std::vector<int> vec{1, 2, 3};
// int y{3.14}; // error
Widget w{}; // object, not function
// ❌ Cons
std::vector<int> v{10}; // one element, not size 10
auto x = {1, 2, 3}; // initializer_list
Pros summary:
| Pro | Notes |
|---|---|
| Consistency | Same {} for scalars and containers |
| Safety | Narrowing often rejected |
| Clarity | Widget w{} is an object |
| Convenience | map/vector init in one place |
Cons summary:
| Con | Mitigation |
|---|---|
initializer_list wins for {} | Sometimes use () |
vector size confusion | vector<int>(n) for size |
auto + = with {...} | Prefer explicit types |
Recommendations
// ✅ Good fits
std::vector<int> vec{1, 2, 3};
Point p{10, 20};
int x{value}; // when you want narrowing checks
// ⚠️ Use parentheses when clearer
std::vector<int> v(100); // 100 elements
std::string s(10, 'x'); // ten 'x' characters
Patterns
Settings object
struct ServerConfig {
std::string host = "localhost";
int port = 8080;
int maxConnections = 100;
bool enableSSL = false;
};
ServerConfig cfg{
"0.0.0.0",
3000,
500,
true
};
Return struct
struct Result {
bool success;
std::string message;
int code;
};
Result processRequest() {
if (error) {
return {false, "Error occurred", 500};
}
return {true, "Success", 200};
}
Member defaults
class Connection {
std::string host_{"localhost"};
int port_{8080};
std::vector<std::string> options_{
"keepalive=true",
"timeout=30"
};
public:
Connection() = default;
Connection(std::string host, int port)
: host_{std::move(host)}, port_{port} {}
};
FAQ
Q1: When should I use uniform initialization?
A: For listing elements in containers, when narrowing checks matter, and when you want a consistent style—while remembering vector and initializer_list rules.
Q2: () vs {}?
A: {}: narrowing checks, initializer_list ctor preference, MVP fix. (): usual ctor calls, e.g. vector size.
Q3: What is narrowing?
A: Conversions that lose information (e.g. double → int, long long → int).
Q4: What is the Most Vexing Parse?
A: Widget w(); looks like a function declaration. Use Widget w{};.
Q5: Does initializer_list always win?
A: For {}, the initializer_list constructor is strongly preferred when it exists.
std::vector<int> v1{10}; // one element 10
std::vector<int> v2(10); // size 10
Q6: What does empty {} mean?
A: Value initialization: scalars to zero-like values; class types call the default constructor if available.
Q7: Performance?
A: Same as other initialization forms at runtime; no inherent overhead.
Q8: Further reading?
A: Scott Meyers, Effective Modern C++ (Item 7); cppreference — List initialization.
Related: List initialization, initializer_list, Value initialization.
In one sentence: Uniform initialization uses braces for consistent, often safer initialization and catches many narrowing mistakes.
Related posts
- C++ initializer_list
- C++ initializer_list constructor
- C++ call_once
Practical tips
- Check compiler warnings first when something looks wrong.
- Reproduce with a minimal example.
Performance
- Do not optimize without profiling.
- Define measurable goals first.
Code review
- Match team conventions for
()vs{}.
Checklist
Before coding
- Is this the right tool for the problem?
- Will teammates understand and maintain it?
- Does it meet performance needs?
While coding
- Warnings addressed?
- Edge cases considered?
- Error handling appropriate?
At review
- Intent clear?
- Tests sufficient?
- Documented where needed?
Keywords
C++, uniform initialization, list initialization, narrowing, C++11.
More links
- C++ call_once
- C++ Aggregate initialization
- C++ Aggregate types
- C++ async & launch