In C++17 code, I sometimes see something like:
void myfunction(const std::string& arg1, int arg2) {
//...
}
void otherfunction() {
myfunction("a",1);
myfunction({},2);
myfunction("b",{});
myfunction({},{});
}
What do the empty curly brace arguments means?
I found that I get an empty string and 0 integer, so I suppose it's some kind of default value.
This was introduced in C++17 and is called list-initialization.
When the braces are empty, it will perform value initialization:
For scalar types such as int, it will be as if you define it in global scope, i.e it will be zero-initialized (also true for class instances with default constructor that is not user-provided and not deleted, unless it has a non-trivial default constructor in which case it will be default initialized).
If class type with no default constructor or if the default constructor has been marked deleted, it will perform default initialization (ex. T t;), this is also true if there is a user defined constructor.
Examples:
struct A {
A() : x{1}, y{2} {}
int x,y;
};
/* A has a default user-provided constructor,
* calls default constructor */
A a{};
//////////////////////////
struct B {
B() {}
int x, y;
string z;
};
/* B has a user defined default constructor,
* since x and y are not mentioned in the initializer list,
* B::x and B::y contain intermediate values,
* while, B::z will be default initialized to "" */
B b{};
//////////////////////////
struct C {
int x, y;
};
/* C has an implicit default constructor,
* C::x and C::y will be zero-initialized */
C c{};
//////////////////////////
Related
The following code, as far as I can tell, correctly initializes the variables of the derived class B:
#include <utility>
struct A {
int i;
};
struct B : A {
int j;
explicit B(A&& a) : A(std::move(a)), j{i} { }
};
int main()
{
A a{3};
B b(std::move(a));
return 0;
}
Running cppcheck with --enable=all gives the warning:
[test.cpp:9]: (warning) Member variable 'A::i' is not initialized in
the constructor. Maybe it should be initialized directly in the class
A?
Is there a reason for this (false, I think) warning?
Yes, this looks like a false positive. Base class subobjects are initialized before direct member subobjects and A(std::move(a)) will use the implicit move constructor which initializes this->i with a.i, so this->i will be initialized before the initialization of this->j is performed (which reads this->i).
The argument given to the constructor in main is also completely initialized via aggregate initialization, so a.i's value will not be indeterminate either.
How do I find out what exactly my classes' default constructors, destructors, and copy/move constructors/assignment operators do?
I know about the rule of 0/3/5, and am wondering what the compiler is doing for me.
If it matters, I'm interested in >=C++17.
The implicitly-defined copy constructor
... performs full member-wise copy of the object's bases and non-static members, in their initialization order, using direct initialization...
For a simple structure:
struct A
{
int x;
std::string y;
double z;
};
The copy-constructor would be equivalent to:
A::A(A const& otherA)
: x(otherA.x), y(otherA.y), z(otherA.z)
{
}
It just calls the respective operation for every member and direct base class, nothing else.
E.g. the implicitly generated copy assignment calls copy assignment for every member.
Note that:
virtual bases are always initialized by the most-derived class. Any initializers for the virtual bases in the member-init-lists of any bases are ignored. Example:
struct A
{
int x;
A(int x) : x(x) {}
};
struct B : virtual A
{
B()
: A(1) // This is ignored when constructing C.
{}
};
struct C : B
{
C()
: A(2) // If A is not default-constructible, removing this causes an error.
{}
};
C c; // .x == 2
The implicitly generated default constructor has a unique property (shared by a default constructor that's explicitly =defaulted in the class body): if the object was created using empty parentheses/braces, any field that would otherwise be uninitialized is zeroed. Example:
struct A
{
int x;
// A() = default; // Has the same effect.
};
A f; // Uninitialized.
A g{}; // Zeroed.
A h = A{}; // Zeroed.
A i = A(); // Zeroed.
This applies to scalar types, and the effect propagates recursively through member class instances that have the same kind of default constructors.
I'm testing the piece of code below:
#include <iostream>
struct foo
{
foo() {}
int a;
};
struct bar
{
bar();
int b;
};
bar::bar() = default;
int main()
{
foo f{};
bar b{};
std::cout << f.a << '\t' << b.b << std::endl;
}
The output is 0 21946.
Well, it seems that the object f is initialized with Zero initialization but the object b is initialized with default initialization. The default initialization of an int is a random number and that's why I got a 21946.
Why did I get two different kinds of initialization here?
I knew that the variable with static storage duration may be initialized with Zero initialization and it put at the .bss segment, this is a kind of static initialization. But foo f{} is obviously a kind of dynamic initialization. Why is f initialized with Zero initialization, instead of default initialization? Why does the compiler make two different behavior?
From cppreference:
Defaulted default constructor outside of class definition (the class must contain a declaration (1)). Such constructor is treated as user-provided
Therefore, none of the statements are value-initializations.
Both are default initialization because they have user-provided constructors. And the values of non-static data members leaves uninitialized (can contain an arbitrary number, including zero)
To do the value initialization for the defaulted constructor case, use the in-class declaration:
struct bar
{
bar() = default;
int b;
};
I'm curious why const members can be modified in the constructor.
Is there any standard rule in initialization that overrides the "const-ness" of a member?
struct Bar {
const int b = 5; // default member initialization
Bar(int c):b(c) {}
};
Bar *b = new Bar(2); // Problem: Bar::b is modified to 2
// was expecting it to be an error
Any ideas?
This is not modification (or assignment) but initialization. e.g.
struct Bar {
const int b = 5; // initialization (via default member initializer)
Bar(int c)
:b(c) // initialization (via member initializer list)
{
b = c; // assignment; which is not allowed
}
};
The const data member can't be modified or assigned but it could (and need to) be initialized via member initializer list or default member initializer.
If both default member initializer and member initializer are provided on the same data member, the default member initializer will be ignored. That's why b->b is initialized with value 2.
If a member has a default member initializer and also appears in the member initialization list in a constructor, the default member initializer is ignored.
On the other hand, the default member initializer takes effect only when the data member is not specified in the member initializer list. e.g.
struct Bar {
const int b = 5; // default member initialization
Bar(int c):b(c) {} // b is initialized with c
Bar() {} // b is initialized with 5
};
Adding to songyuanyao's great answer, if you want a const data member that you can't initialize in a constructor, you can make the member static:
struct Bar {
static const int b = 5; // static member initialization
Bar(int c)
:b(c) // Error: static data member can only be initialized at its definition
{
b = c; // Error: b is read-only
}
};
In C++17 you can improve this further by making it inline:
struct Bar {
inline static const int b = 5; // static member initialization
Bar(int c)
:b(c) // Error: static data member can only be initialized at its definition
{
b = c; // Error: b is read-only
}
};
This way you won't have problems with ODR.
When you do:
struct Bar {
const int b = 5; // default member initialization
...
};
You are telling the compiler to do this with the default constructor:
...
Bar() : b(5)
{}
...
Irrespective of if you've provided the default constructor or not. When you do provide default-constructor and the initial assignment, you override compiler's default assignment code (i.e. b(5)).
Default initialization/assignment at the declaration is useful when you have multiple constructors, and you may or may not assign the const members in all constructors:
...
Bar() = default; // b=5
Bar(int x) : b(x) // b=x
Bar(double y) : /*other init, but not b*/ // b=5
...
This is my code and you can also run it from http://cpp.sh/5lsds
#include "iostream"
using namespace std;
class X{
private:
int c;
public:
X(){}
X(int b){
c = 11;
}
int getC();
};
class Z:public X{
public:
Z(int n){
X(23);
}
};
int main()
{
Z z(1);
cout<<z.getC()<<endl;
return 0;
}
int X::getC(){
return c;
}
I need to have X(){} line since the child constructor needs to call the parent default constructor.
If you run the program from http://cpp.sh/5lsds you can see that the output is 0 while I expect it to be 11. Since the Z constructor calls X constructor with an int parameter and it sets the c value to 11 but the output is 0.
You should use member initializer list,
In the definition of a constructor of a class, member initializer list specifies the initializers for direct and virtual base subobjects and non-static data members.
e.g.
Z(int n) : X(23) {}
I need to have X(){} line since the child constructor needs to call the parent default constructor.
With member intializer list it's not required again (in this code sample).
For X(23); in the body of the constructor, you're just creating a temporary X, which has nothing to do with the base subobject X of Z; Then the default constructor of X (i.e. X::X()) will be used for it. i.e. it's equivalent with:
Z(int n) : X() { // initialize the base suboject X via X::X()
X(23); // create an unnamed temporary via X::X(int)
}
You don't invoke the constructor of the base class
Z(int n){
X(23);
}
This creates an unnamed temporary X object, and passes 23 to its constructor. It doesn't construct the X sub-object of Z.
In C++, we construct bases and members using the member initializer list syntax:
X(int b) :
c(11)
{}
Z(int n) :
X(23)
{}
The member initializer list syntax is pretty much equivalent to the assignment you do when a simple integer is the constructed member. But beware that more complex sub-objects will be default constructed first, and then have their assignment operator invoked. That could make a substantial (worsening) difference in performance to just specifying them in the member initializer list and constructing once.