I'm a bit confused about when and how to initialize members of a class in a constructor. If I understand things correctly, you can accomplish this by either including default values in the class definition, a constructor member initialiser list, or via assignment in the body of the constructor. I realise that it's best to initialize before entering the body of the constructor, but when should you use a member initialiser list and when should you use default values?
Apologies if I've fundamentally misunderstood something here.
Your are really confusing things a bit. There are at least 4 different concepts
default arguments
initializer list
initialization in constructor body (even constructors can have a non-trivial body)
inline member initialization[1]
you made 3 of.
In the following examples (at ideone.com) I use struct instead of class and inline constructors for brevity/simplicitly.
#include <iostream>
struct A
{
A(int v=1): m(v) {}
int m;
};
struct B
{
B(): m(2) {}
int m;
};
struct C
{
C()
{
m = 3;
}
int m;
};
// since C++11
struct D
{
int m = 11;
};
int main()
{
using namespace std;
#define TRACE(arg) cout << #arg ": " << arg.m << endl;
A a1;
TRACE(a1)
A a2(4);
TRACE(a2)
B b;
TRACE(b)
b.m = 5;
TRACE(b)
C c;
TRACE(c)
c.m = 6;
TRACE(c)
D d;
TRACE(d)
d.m = 0;
TRACE(d)
#undef TRACE
}
The usage of A differs from that of B, C and D.
Only A provides a way to initialize m with a value other than the default. All variants provide direct access to m (mostly not a good choice, see it as a placeholder for a more sophisticated way to modify a member). You have to modify an object if there is no access to the actual initializer value(s). This makes m for constant objects of B, C and D effectively a compile-time constant. Member "initialization" in the constuctor body (C) should be avoided if an initializer list (A or B) can be used (sometimes this is not as trivial as shown here).
Option D should be estimated if you are using C++11 or later.
So A seems the most flexible option. But it's not a good pattern for initializing two or more members: it's too easy to mix up the parameter order.
[1] this option was added after I (thanks to NikosC) realized that this options existed
Related
I discovered a query way of initialising a class member q by assigning it to a different class member that is initialised through the constructor i:
class test{
public:
test(int c) : i(c) {
}
int i;
int q = i;
};
int main() {
std::cout << test(1).q << std::endl; //outputs 1
}
However if I swap around the order of declaration of i and q:
int q = i;
int i;
Printing out i outputs 0.
Is it a bad idea to initialise class members this way? Clearly in this example its probably easier to just add : i(c), q(c) to the constructor.
But for situations where there is a class member that rely on i e.g. some object with a constructor that takes a int (i) and has a private = operator and copy constructor might this approach be a good way of initialising that object? Or is this a bad idea and should always be avoided?
Yes, you've outlined the conditions of failure. It's not hard to imagine someone unintentionally breaking your code due to reordering.
If you wanted to do something like this, it's best to assign both in the constructor from the input source.
test(int c) : q(c), i(c) {}
This has less confusion about initialization order and the two variables are no longer dependent.
I know it's a subject that has been discussed quite extensively here on stackoverflow but I've a hard time finding thorough answers removing all the confusion I've concerning list initialization and initializer_lists in C++, so I will just give it a try and ask my own questions.
Please, consider the following snippet of code :
class C
{
public :
C(int a, int b, int c) : _a (a), _b(b), _c(c) {}; //initialization_list with ()
//C(int a, int b, int c) : _a{ a }, _b{ b }, _c{ c } {}; //initialization list with {}
private :
int _a, _b, _c;
};
int main()
{
C a(5.3,3.3,4.3); // no list
C b{5.3,3.3,4.3}; // list {}
C c({5.3,3.3,4.3}); // list {}
}
I don't understand why those two initialization lists behave similarly? I was expecting, when trying to create the object a of type C using the initialization list of type _a{a}, _b{b}, _c{c} to get a compiler error about narrowing. However, no errors are generated and _a, _b and _c just store the integer values.
It's only when creating the objects b or c using a list "{}" that the compiler generates a narrowing error message. Why is that? Is there any differences between writing an initialization list using {} or () that I'm unaware of or are the behaviours identical?
Come my next question:
class C
{
public :
//private :
int _a, _b, _c;
};
int main()
{
C a(5,3,4); //obviously doesn't work as no such constructor
C b{5,3,4}; //work only if _a, _b and _c are not private nor protected!
}
How comes that the second statement (with braces) only works if the variables are public? What is the mechanism involved?
So I would like to better understand, beside the "narrowing safety" provided by creating an object with a list {}, what other "functionalities" does this list mechanism provide ? Because in the second call, it's still the default constructor that is called (hence, not a default constructor taking an initializer_list as argument), right ?
Lastly, imagine in my class C I've another constructor taking an initializer list as parameter.
class C
{
public :
C() = default; //default constructor
C(int a, int b, int c) : _a (a), _b(b), _c(c) {};
C(std::initializer_list<int> a) { //do some stuffs with the list};
private :
int _a, _b, _c;
};
It's pretty obvious that, if trying to create an object taking any numbers of integers but 3 (or 0 actually), the constructor taking the initializer_list will be invoked. If creating an object like that however :
C c();
or
C c{};
the default constructor will be called. But if creating an object with exactly 3 integers :
C c(5,2,3);
or
C c{5,2,3};
the initializer_list constructor will be called. The rule goes like that :
If either a default constructor or an initializer-list constructor could be invoked, prefer the default constructor
If both an initializer-list contructor and an "ordinary constructor" could be invoked, prefer the initializer-list constructor
Therefore (and correct me if I'm wrong), if I create my object like that :
C c{5,3,4};
The iniatializer-list constructor will be called. However, if I create my object like that :
C c(5,3,4);
The second constructor (taking 3 ints as arguments) will be called. My question is : how can I create an object with this second constructor instead of the iniatializer-list one if I want to provide narrowing safety as well ? (because if I do as in the first example of this question, the initializer-list constructor will be called instead !).
Don't hesitate to examplify your replies and to discuss on list-related concepts I haven't talk in this question. I would like to get a very good grasp on those. Thanks.
So anytime curly braces are used you are using aggregate initialization, a method of initialization for structs or classes that initializes in order., or via a designator. For example,
#include <iostream>
struct Foo
{
int a;
char b;
};
class Doo
{
public:
double h;
char ch;
};
int main() {
Foo first = {3, 't'};
std::cout << first.b << "\n";
//t
Doo second = {'3', 50};
std::cout << second.ch << "\n";
//2 interpreted as char
}
Here, when we use the {} to initialize a class or struct, they are always interpreted as being in the order listed in the class. That's why '2' was printed since 50 in ASCII corresponds to the character '2'.
Constructor Initialization
So you can also use the same logic with constructor initialization lists,
#include <iostream>
struct Pair
{
int first;
long second;
};
class Couple
{
public:
Pair p;
int g;
public:
Couple(): p{3, 700}, g{3}
{}
};
int main() {
Couple test;
std::cout << test.p.first << "\n";
//3
}
Here, the {3, 700} next to p, would be the same as Pair p = {3, 700}; used else where in code. Your basically using an in order aggregate initialization. Now, what happens if we change the curly braces for the Pair field to parenthesis?
We get this error
main.cpp: In constructor 'Couple::Couple()':
main.cpp:15:26: error: no matching function for call to 'Pair::Pair(int, int)'
Couple(): p(3, 700), g{3}
That's because we don't have a constructor for Pair that accepts two numbers. So the key difference between the aggregate initialization and the parenthesis is you need to have constructors implemented for any specific set of arguments you make with parenthesis, yet with curly braces you can just use the default one the compiler hands you.
The std::initializer_list is a not-commonly used form of container for multiple arguments in initialization lists with {}.
I have two types of structure variable initialization in my code.
Example
#include<iostream>
#include<string>
using namespace std;
struct Data{
int arr[5];
float x;
};
int main(){
struct Data d = {0};
struct Data d1 = {};
cout<<d.arr[0]<<d.x;
cout<<d1.arr[0]<<d1.x<<endl;
return 0;
}
I am running the code ad getting 0 0 0 0 as my output. Please help me, is there any difference between both initialization.
According to the rule of aggregate initialization, the effect is the same here, i.e. all the members of the struct will be value-initialized (zero-initialized here for non-class types).
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
More precisely,
struct Data d = {0}; // initialize the 1st member of Data to 0, value-initialize(zero-initialize) the remaining members
struct Data d1 = {}; // value-initialize(zero-initialize) all the members of Data
Note that the whole story is based on that Data is an aggregate type and its members are non-class types, otherwise the behavior would change according to the rule of list initialization.
The result is the same in this case, but not necessarily so in other cases.
In this case, you haven't provided a ctor, so you're using aggregate initialization. This gives zero-initialization for the empty-init-list, and you're supplying 0 for the non-empty one, so the two work out the same.
If you provided a ctor, it would be trivial to get different results from the two:
#include <iostream>
struct foo {
int f;
foo(int f = 5) : f(f) {}
friend std::ostream &operator<<(std::ostream &os, foo const &f) {
return os << f.f;
}
};
int main() {
foo f1{};
foo f2{0};
std::cout << "Empty init list: " << f1 << "\n";
std::cout << "zero init list: " << f2 << "\n";
}
Although a ctor is the most obvious way to do this, it's not the only one. For another obvious example (C++11 and newer only):
struct foo {
int f = 5;
};
The default initialization using {} is defined as initialization of each member by using {}. So, by doing
struct Data d1 = {};
Data d1 is initialized to {{},{}}, which is {{0, 0, 0, 0, 0}, 0.0},
as 0 and 0.0 are default values for int and float resp.
This is the reason you don't see any difference. Both of them invariably do the same thing in your case.
Differences lie in these cases:
1.) This is when providing initializer inside {} becomes mandatory:
struct X {
X(int);
};
X x1 {}; // error : empty initializer. X(int) states that an int is required to construct an X.
X x2 {0}; // OK
2.) Scenario when zero initialization is forbidden:
struct Test {
string name;
int year;
};
Test alpha0{0}; // Error. Because name in Test fails at zero-initialization.
Test alpha{}; // OK. Because name in Test is default-initialized with empty string "".
Yes, there is a difference. In the first case you're explicitly initializing the first member of Data (arr[0]) to zero. In the second case you aren't initializing anything and simply reading whatever value happens to be there. In this case it was also zero, but that's not guaranteed to work, especially in a more complicated program.
It's always a good idea to initialize all the members of a struct. Consider this slightly modified version of your program which should make what's happening clear:
#include<iostream>
#include<string>
using namespace std;
struct Data{
int arr[5];
float x;
};
int main(){
struct Data d = {1, 2, 3, 4, 5, 3.14f};
struct Data d1 = {};
cout<<d.arr[0]<<", "<<d.x<<", ";
cout<<d1.arr[0]<<", "<<d1.x<<endl;
return 0;
}
This will print:
1, 3.14, 0, 0
I'd like to understand what's the differences of using one form rather than the other (if any).
Code 1 (init directly on variables):
#include <iostream>
using namespace std;
class Test
{
public:
Test() {
cout<< count;
}
~Test();
private:
int count=10;
};
int main()
{
Test* test = new Test();
}
Code 2 (init with initialization list on constructor):
#include <iostream>
using namespace std;
class Test
{
public:
Test() : count(10) {
cout<< count;
}
~Test();
private:
int count;
};
int main()
{
Test* test = new Test();
}
Is there any difference in the semantics, or it is just syntactic?
Member initialization
In both cases we are talking about member initialization.
Keep in mind that the members are initialized in the sequence in which they are declared in the class.
Code 2: Member initializer list
In the second version:
Test() : count(10) {
: count(10) is a constructor initializer (ctor-initializer) and count(10) is a member initializer as part of the member initializer list. I like to think of this as the 'real' or primary way that the initialization happens, but it does not determine the sequence of initialization.
Code 1: Default member initializer
In the first version:
private:
int count=10;
count has a default member intitializer. It is the fallback option. It will be used as a member initializer if none is present in the constructor, but in the class the sequence of members for initialization is determined.
From section 12.6.2 Initializing bases and members, item 10 of the standard:
If a given non-static data member has both a
brace-or-equal-initializer and a mem-initializer, the initialization
specified by the mem-initializer is performed, and the non-static data
member’s brace-or-equal-initializer is ignored. [ Example: Given
struct A {
int i = / some integer expression with side effects / ;
A(int arg) : i(arg) { }
// ...
};
the A(int) constructor will simply initialize i to the value of arg,
and the side effects in i’s brace-or-equalinitializer will not take
place. —end example ]
Something else to keep in mind would be that if you introduce a non-static data member initializer then a struct will no longer be considered an aggregate in C++11, but this has been updated for C++14.
Differences
what's the differences of using one form rather than the other (if
any).
The difference is the priority given to the two options. A constructor initializer, directly specified, has precedence. In both cases we end up with a member initializer via different paths.
It is best to use the default member initializer because
then the compiler can use that information to generate the constructor's initializer list for you and it might be able to optimize.
You can see all the defaults in one place and in sequence.
It reduces duplication. You could then only put the exceptions in the manually specified member initializer list.
In the C++ Core Guidelines (see note 1 below), Guideline C.48 recommends the first approach (in-class initializers.) The reasoning provided is:
Makes it explicit that the same value is expected to be used in all constructors. Avoids repetition. Avoids maintenance problems. It leads to the shortest and most efficient code.
In fact if your constructor does nothing but initialize member variables, as in your question, then Guideline C.45 is firmer still, saying to use in-class initializers for sure. It explains that
Using in-class member initializers lets the compiler generate the function for you. The compiler-generated function can be more efficient.
I am not going to argue with Stroustrup, Sutter, and several hundred of their friends and colleagues even if I haven't written a compiler so I can't prove it's more efficient. Use in-class initializers wherever you can.
If you're not familiar with the guidelines do follow the links to see sample code and more explanations.
The difference I can think of is that member initializer list is prior to default member initializer.
Through a default member initializer, which is simply a brace or
equals initializer included in the member declaration, which is used
if the member is omitted in the member initializer list.
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.
For example:
class Test
{
public:
Test() {} // count will be 10 since it's omitted in the member initializer list
Test(int c) : count(c) {} // count's value will be c, the default member initializer is ignored.
private:
int count = 10;
};
There is no difference in the code. The difference would come if you would be would have more than one constructor overload and in more than one count would be 10. With the first version you would have less writing to do.
class Test
{
public:
Test() = default;
Test(int b) : b(b) {} // a = 1, c = 3
~Test();
private:
int a = 1;
int b = 2;
int c = 3;
};
As opposed to the second version where the above code would look like this:
class Test
{
public:
Test() : a(1), b(2), c(3) {}
Test(int b) : a(1), b(b), c(3) {}
~Test();
private:
int a;
int b;
int c;
};
The difference gets bigger with more member variables.
When you initialise next to the declaration of the member, this is valid only in C++11 onwards, so if you're in C++98/03 you outright cannot do this.
If the value never changes, you could choose to make this a constexpr static instead and the compiler would then be required to not use any extra storage for the value (so long as you don't define it) and instant use constant propagation wherever the value is used instead.
One disadvantage of using the by-declaration syntax is that it must be in the header, which will result in a recompile of all translation units that include the header every time you want to change its value. If this takes a long time, that might be unacceptable.
Another difference is that using the member initialisation list lets you change the value for each constructor, whilst using the by-declaration version only allows you to specify one value for all constructors (although you could overwrite this value ... but I'd personally avoid this as it could get quite confusing!).
As an aside, there's no need to use new here to create an instance of Test. This is a common mistake when people come to the language from other languages and I wanted to make you aware. There are of course many uses for doing this outside of your example.
I have a custom class that I want to behave like a built-in type.
However I have noticed that you can initialise a const variable of that class without providing an initial value. My class currently has an empty default constructor.
Here is a comparison of int and my class foo:
int a; // Valid
int a = 1; // Valid
const int a = 1; // Valid
const int a; // Error
foo a; // Valid
foo a = 1; // Valid
const foo a = 1; // Valid
const foo a; // Should cause an error, but it compiles
As you can see I need to prevent
const foo a;
from compiling.
Any ideas from C++ gurus?
It compiles only if it has a default constructor, and it compiles because it has it, which means that it is initialized. If you don't want that line to compile, just disable the default constructor (will also make foo a; an error as an unwanted side effect). Without a definition of foo or what you want to do, this is as far as I can get.
I don't think there is any way of achieving what you want (i.e. allow the non-const variable to be default initialized, while having the const version fail compilation and allowing the other use cases --that require providing constructors)
The rules of C++ simply say that default-initialization (e.g. new T;) and value-initialization (e.g. new T();) are the same for objects of class type, but not for objects of fundamental type.
There's nothing you can do to "override" this distinction. It's a fundamental part of the grammar. If your class is value-initializable, then it is also default-initializable.
There is a sort-of exception for classes without any user-defined constructors: In that case, initialization of members is done recursively (so if you default-init the object, it tries to default-init all members), and this will fail if any of the class members are themselves fundamental, or again of this nature.
For example, consider the following two classes:
struct Foo { int a; int b; };
struct Goo { int a; int b; Goo(){} };
//const Foo x; // error
const Goo y; // OK
The implicit constructor for Foo is rejected because it doesn't initialize the fundamental members. However, y is happily default-initialized, and y.a and y.b are now "intentionally left blank".
But unless your class doesn't have any user-defined constructors, this information won't help you. You cannot "forward" the initialization type to a member (like Foo() : INIT_SAME_AS_SELF(a), b() { }).