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
...
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.
in C++, what is the difference between initialization list and assigning values in a constructer rather than the way each method looks?
I mean what's the advantage of using one rather than the other and why in the given example in the slide (below) only works with initialization? (I hope if you could add some resources to it since I didn't find)
Click here to view slide: uploaded on imgur
Using initialization list in constructor is the one step process i.e. it initializes objects at the moment it’s declared. It calls copy constructor.
Whereas using assignment is the two-step process i.e. define the object and then assign it. Defining objects calls default constructor and then assignment calls assignment operator. Hence, expensive operations.
In C++, constant or reference data member variables of a class can only be initialized in the initialization list, not using assignment in constructor body.
Both constant and reference data member variables have property that they both must be initialized at the moment of declaration. So, there is only way to use initialization list in constructor, as initialization list initializes class member variables at the time of declaration whereas assignment if constructor body initializes data members after declaration.
There are situations where initialization of data members inside constructor doesn’t work and Initializer List must be used. Following are such cases.
For initialization of non-static const data members.
#include<iostream>
using namespace std;
class Test {
const int t;
public:
Test(int t):t(t) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
Test t1(10);
cout<<t1.getT();
return 0;
}
For initialization of reference members.
#include<iostream>
using namespace std;
class Test {
int &t;
public:
Test(int &t):t(t) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
int x = 20;
Test t1(x);
cout<<t1.getT()<<endl;
x = 30;
cout<<t1.getT()<<endl;
return 0;
}
For initialization of member objects which do not have default constructor. (In your case Array digits does not have default constructor)
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << "A's Constructor called: Value of i: " << i << endl;
}
// Class B contains object of A
class B {
A a;
public:
B(int );
};
B::B(int x):a(x) { //Initializer list must be used
cout << "B's Constructor called";
}
int main() {
B obj(10);
return 0;
}
For initialization of base class members.
When constructor’s parameter name is same as data member.
For Performance reasons.
If you don't use an initialization list, the data members of the class will be default constructed before the body of the constructor is reached:
class Foo{
private:
int bar;
public:
Foo(int _bar){//bar is default constructed here
bar = _bar; //bar is assigned a new value here
}
};
This isn't a big issue for a fundamental-type, like int, as the default constructor is not expensive. However, it can become an issue if the data member does not have a default constructor, or default construction followed by assignment is more expensive than direct construction:
//Bar does not have a default constructor, only a copy constructor
class Bar{
public:
Bar() = delete; //no default constructor
Bar(const Bar& bar); //copy constructor only
};
class Foo{
private:
Bar bar;
public:
Foo(const Bar& _bar){
//this will not compile, bar does not have a default constructor
// or a copy assignment operator
bar = _bar;
}
Foo(const Bar& _bar) : bar(_bar){//this will compile, copy constructor for bar called
}
};
Generally speaking, use the initialization list for more efficient code.
Someone gave me (part of) the following code:
struct MyStruct
{
int x = {};
int y = {};
};
I never saw this syntax before, what does initialization with {} mean?
This is default member initializer (since C++11),
Through a default member initializer, which is a brace or equals initializer included in the member declaration and is used if the member is omitted from the member initializer list of a constructor.
The initialization itself is copy-list-initialization (since C++11), as the effect, the data member x and y would be value-initialized (and zero-initialized as built-in type) to 0.
Since the C++11 standard there are two ways to initialize member variables:
Using the constructor initialization list as "usual":
struct Foo
{
int x;
Foo()
: x(0)
{
}
};
Use the new inline initialization where members are getting their "default" values using normal initialization syntax:
struct Foo
{
int x = 0;
};
Both these ways are for many values and types equivalent.
given the following code:
class Class {
int x;
public:
Class() = default;
};
I get the following warning:
Member 'x' was not initialized in this constructor
What is the reason of this warning?
x is an int and its default initialization leaves it with an indeterminate value. The simplest, most uniform fix is to value-initialize it in its declaration.
class Class {
int x{}; // added {}, x will be 0
public:
Class() = default;
};
You could also do int x = 5; or int x{5}; to provide a default value
What is the reason of this warning?
The defaulted default constructor does not initialize any members of the fundamental types. Hence, x is left uninitialized.
You can fix the problem by using either the member initialization method in the constructor
Class() : x(0) {}
or in-class member initilaization.
int x = 0;
Class() = default;
I have a in-class initialized const member in a derived class which I'd like to pass to the constructor of the base class.
Example:
class Base{
public:
Base(int a) : i(a){}
private:
int i;
};
class Derived : Base{
public:
Derived() : Base(a){}
private:
const int a = 7;
};
int main(){
Derived d;
}
However this spawns an uninitialized error:
field 'a' is uninitialized when used here [-Wuninitialized]
I was under the impression that const initializing it would set the value directly allowing it to be passed from the derived ctor in this manner. Am I doing something wrong or am I under the wrong impression? When are the const in-class initialized members initialized?
When initializing base classes and class members during creation of an object, the order of initialization is:
Virtual base classes, in tree order
Direct non-virtual base classes, in order
Non-static data members, in their order of declaration.
So Base(a) happens before a = 7 happens.
One way to fix this would be to make a be static const or static constexpr. This is probably a good idea anyway, because non-static const variables make your class more difficult to use. (e.g. there will not be an implicitly-generated copy-constructor).
Your question,
When are the const in-class initialized members initialized?
is a bit of a red herring. "in-class initialized" doesn't really mean anything; the brace-or-equal initializer is essentially just syntactic sugar and takes the place of the corresponding constructor initalizer list slot. const also has no special bearing. So the real question should be:
When are non-static data members initialized?
The details don't actually matter so much, suffice to say that non-static data members are initialized after base subobjects are initialized, so your proposed construction cannot work.
The straight-forward answer is not to use a brace-or-equals-initializer and just use a normal (possibly defaulted) constructor parameter. Here are a few examples:
struct Foo : Base
{
const int a;
// Default constructor, mention value only once
Foo(int _a = 10) : Base(_a), a(_a) {}
// DRYolent default constructor
Foo() : Base(10), a(10) {}
// Delegating default constructor
Foo() : Foo(10) {}
private: Foo(int _a) : Base(_a), a(_a) {}
};
Alternatively, if the value of the constant doesn't need to be configurable, then you can make it a per-class constant (rather than per-object):
struct Foo : Base
{
static const int a = 10;
Foo() : Base(a) {}
};
const int Foo::a; // only if you ODR-use Foo::a