Skip to content

akrzemi1 Value-initialization with C++

NOTE:

1、这篇文章对比了value initialization 和 default initialization

2、遵循 CppCoreGuidelines ES.20: Always initialize an object,阅读完成后,我的一个想法是: 我们总是需要value initialization,C++11 的 uniform initialization {} 让我们以uniform、consistent syntax来实现 value initialization。

Some time ago, I showed how boost::value_initialized can be used to value-initialize objects in generic components in C++03. In C++11 it is practically not necessary. C++11 gives you a way to force value-initialization of your objects in any possible initialization context.

NOTE: 这就是C++11的uniform initialization,参见C++11-List-initialization

Value initialization

NOTE: 本节其实重点是区分value initialization和default initialization。

Are you familiar with term “value initialization”? It is sometimes confused with “default-initialization”. In the following declaration:

#include <string>
#include <mutex>
#include <iostream>

struct widget
{
    int x;
    int y;
    std::string title;
};

void default_initialization()
{
    int i;         // default-initialize i
    std::cout << "i:" << i << std::endl;
    std::mutex m;  // default-initialize m
    widget w;      // default-initialize w
    std::cout << "w.x:" << w.x << std::endl;
    std::cout << "w.y:" << w.y << std::endl;
    std::cout << "w.title:" << w.title << std::endl;
    int a[9];      // default-initialize a
    std::cout << "a[0]:" << a[0] << std::endl;
    std::cout << "a[1]:" << a[1] << std::endl;
    std::cout << "a[2]:" << a[2] << std::endl;
    std::cout << "a[3]:" << a[3] << std::endl;
}

int main()
{
    default_initialization();

    return 0;
}
// g++ --std=c++11 test.cpp

NOTE: 上述程序的输出如下:

i:0
w.x:2
w.y:0
w.title:
a[0]:256
a[1]:64
a[2]:4196512
a[3]:0

i is default-initialized. Its value is indeterminate (no initialization is performed). This is so for performance reasons, because sometimes we do not need an initial value. For instance:

int i;         // uninitialized
std::cin >> i; // read initial value

Let’s continue with our example: m is also default initialized. But because std::mutex provides a default constructor, this constructor is chosen for default-initialization.

For w, because type widget does not provide a custom default constructor, an implicit default constructor is generated (and called here) by the compiler: it calls default constructors for member sub-objects, which perform no initialization for x and y (as they are ints) and calls user-provided default constructor for title. For a, each element of the array is default-initialized (left in indeterminate state).

NOTE:

1、对于non-class type(比如int),它们的default initialization(或者说 default constructor)是do-nothing,显然这是和C兼容的

As we can see, for built-in types, default-initialization leaves the objects in indeterminate state. Value-initialization enables us to tell that for built-in types, objects should be initialized with value 0.

NOTE: 上面已经说明了default-initialization和value-initialization的一个重要差异: 对于built-in type:

Initialization
Default-initialization default-initialization leaves the objects in indeterminate state
Value-initialization objects should be initialized with value 0

Initialization list of the constructor

We were able to use it in C++03 in certain contexts:

#include <string>
#include <mutex>
#include <iostream>

struct widget
{
    int x;
    int y;
    std::string title;
};

struct value_initialization
{
    int i;
    std::mutex m;
    widget w;
    int a[9];

    value_initialization() :
            i() // value-initialize i
            , m() // value-initialize m
            , w() // value-initialize w
            , a() // value initialize a
    {
    }
};

int main()
{
    value_initialization v;
    std::cout << "i:" << v.i << std::endl;
    std::cout << "w.x:" << v.w.x << std::endl;
    std::cout << "w.y:" << v.w.y << std::endl;
    std::cout << "w.title:" << v.w.title << std::endl;
    std::cout << "a[0]:" << v.a[0] << std::endl;
    std::cout << "a[1]:" << v.a[1] << std::endl;
    std::cout << "a[2]:" << v.a[2] << std::endl;
    std::cout << "a[3]:" << v.a[3] << std::endl;
    return 0;
}
// g++ --std=c++11 test.cpp

NOTE: 上述程序的输出如下:

i:0
w.x:0
w.y:0
w.title:
a[0]:0
a[1]:0
a[2]:0
a[3]:0

In initialization list of the constructor () means “value-initialize”. That is: initialize i with value 0. For m, call its default constructor. For w, its members x and y are initialized with value 0, and for title we call string’s default constructor. Integers in a are initialized with value 0.

Automatic objects

If we wanted to trigger such value initialization for automatic objects in C++03, it is possible, but we have to select the appropriate initialization syntax for each object:

void manual_value_initialization()
{
  int i = 0;     // "=0" for scalars
  std::mutex m;  // nothing here
  widget w = {}; // aggregate initialization
  int a[9] = {}; // aggregate initialization
}

But now, what do you do if you are in a function template:

template <typename T>
void manual_value_initialization()
{
  T t = /* TYPE WHAT ? */;
}

Since T can be int, std::mutex, widget or int[9], which syntax do you pick for value-initialization? None will work for all four types.

Value-initialization syntax

In C++11 the last problem is solved with the “brace” syntax:

template <typename T>
void cpp11_value_initialization()
{
  T t {}; // value-initialize
}

{} just means “value-initialize”, but unlike (), it works in any initialization context.

Going back to our example with class called value_initialization, it is risky to put built-in types as members in a class, because you may forget to call their value initialization in the constructor(s). Especially, when you add a new member, and the body of the constructor is in another “cpp” file, you may forget to add value-initialization in all constructors. In C++11, you can fix that problem:

struct cpp11_value_initialization
{
  int i {};
  std::mutex m {};
  widget w {};
  int a[9] {};

  // no default constructor required
};

NOTE: if we used () to “initialize” member in the last example, we would be in fact declaring a member function. This is why initialization with () is not uniform.

显然,使用()无法实现initialization grammar的uniform。

Syntax int i {}; here means, “unless specified otherwise in the constructor, value-initialize member i upon initialization.” Similarly, in “generic context”:

template <typename T>
class C
{
  T member {};
  // ...
};

Note that if a class defines (implicitly or not) a default constructor, empty braces are not interpreted as zero-element initializer list (see this post for more details).

NOTE: 下面是作者添加的更新:

Update. I made an error in the original text: empty braces are sometimes treated as a zero-size initializer list.

This is why the braced initialization syntax is called “uniform initialization”: because it works the same in different (all) initialization contexts: in constructor initialization list, for declaring automatic, temporary, global, heap-allocated variables, for class members. Note that if we used () to “initialize” member in the last example, we would be in fact declaring a member function. This is why initialization with () is not uniform.

“Uniform” does not mean that () and {} will do the same thing. For an example, see this post.

The empty brace syntax enables a funny-looking way of creating tags. A tag is an empty structure that does not store any value but whose type is used for selecting overloaded constructors of functions. You can find them in the Standard Library, for instance std::allocator_arg, std::nothrow, std::piecewise_construct, std::defer_lock. Such tags can be declared as one-liners:

constexpr struct allocator_arg_t{} allocator_arg{};

The first empty braces indicate that class allocator_arg_t has empty definition; the second indicates that we are value-initializing a global constant.