C++ Constructors vs Initialization Lists speed comparison - c++

Are there any differences in execution time between constructors and initialization lists?(or is it just a matter of coding preference).
I have a set of objects that needs to be created frequently and would like to know if there is any performance gain by using initialization lists instead of constructors.
If I were to create a million instances of class A and another million of class B which choice would be better(the objects represent packets generated within a network hence these numbers).
class A {
private:
int a, b;
public:
A(int a_var, int b_var):a(a_var), b(b_var) {};
};
class B {
private:
int a, b;
public:
B(int a_var, int b_var) {
a = a_var;
b = b_var;
}
};
If any of the constructors is faster than the other for primitive types(as in the example) will it be faster if a and b were to be replaced by types?
Type example:
class AType {
private:
string a, b;
public:
AType(string a_var, string b_var):a(a_var), b(b_var) {};
};

The difference is for types with no trivial default constructor, which is called for you by compiler in your class B. Your class B is equivalent to:
class B {
private:
SleepyInt a, b;
public:
// takes at least 20s
B(int a_var, int b_var) : a(), b()
// ^^^^^^^^^^
{
a = a_var;
b = b_var;
}
};
If you do not place member variable or base class constructor in initialization list - ithe default constructor is called for them. int is basic type - its default constructor costs nothing - so no difference in your example, but for more complex types constructor+assignment might cost more than just constructing.
Some funny example, just to illustrate the difference:
class SleepyInt {
public:
SleepyInt () {
std::this_thread::sleep_for(std::chrono::milliseconds( 10000 ));
}
SleepyInt (int i) {}
SleepyInt & operator = (int i) { return *this; }
};
class A {
private:
SleepyInt a, b;
public:
A(int a_var, int b_var):a(a_var), b(b_var) {};
};
class B {
private:
SleepyInt a, b;
public:
// takes at least 20s
B(int a_var, int b_var) {
a = a_var;
b = b_var;
}
};

It is commonly accepted practice to use initialization lists as opposed to assignment in a constructor, and there's a very good reason for that.
Initialization lists can be used to initialize both POD (Plain Old Data) and user-defined types. When initializing a POD type, the effect is exactly the same as an assignment operator, meaning there is no performance difference between initialization lists or assignment in a constructor for POD types.
When we consider non-POD types things get more interesting. Before the constructor is called, constructors for the parent class and then any contained members are invoked, and by default the no-argument constructor is called. Using an initialization list you are able to choose which constructor is called.
So to answer the question, there is a performance difference, but only when initializing non-POD types.

If members are of more or less complex types, then the assignment initialization will first cause the default constructor call and then operator=, which may take longer.

There won't be a performance improvement if the types are built-in/intrinsic type.
That said:
Conclusion: All other things being equal, your code will run faster if
you use initialization lists rather than assignment.

Initialization list is benefical reference types, member class objects, or const members. Otherwise it takes more time.
Look at my test code:
#include <iostream>
#include <ctime>
using namespace std;
class A{
int a;
public:
A(int a_):a(a_){}
};
class B{
int b;
public:
B(){
}
B(int b_){
b=b_;
}
};
class C{
B b;
public:
C(int c_):b(c_){
}
};
class D{
B b;
public:
D(int d_){
b=d_;
}
};
int main()
{
clock_t start1[10], start2[10], end1[10], end2[10];
for(int j=0;j<10;j++){
start1[j]=clock();
for(int i=0;i<100000;i++){
A *newA=new A(i);
delete newA;
}
end1[j]=clock();
start2[j]=clock();
for(int i=0;i<100000;i++){
B *newB=new B(i);
delete newB;
}
end2[j]=clock();
}
double avg1=0, avg2=0;
for(int i=0;i<10;i++){
avg1+=(end1[i]-start1[i]);
avg2+=(end2[i]-start2[i]);
}
cout << avg1/avg2 << endl;
for(int j=0;j<10;j++){
start1[j]=clock();
for(int i=0;i<100000;i++){
C *newC=new C(i);
delete newC;
}
end1[j]=clock();
start2[j]=clock();
for(int i=0;i<100000;i++){
D *newD=new D(i);
delete newD;
}
end2[j]=clock();
}
avg1=avg2=0;
for(int i=0;i<10;i++){
avg1+=(end1[i]-start1[i]);
avg2+=(end2[i]-start2[i]);
}
cout << avg1/avg2 << endl;
system("pause");
return 0;
}
Example outputs like this:
1.02391
0.934741

Related

Manually calling constructor of base class outside initialization list

I have a Derived class whose constructor has to populate the fields of a struct that is passed as an argument to the constructor of the Base class. I want to be able to name the fields of the struct that I am populating, to keep my code future-proof (i.e.: resistant to addition and/or reordering of the members of MyStruct).
Note that struct MyStruct has default values, so it cannot be initialised with named fields directly in the initialization list (e.g.: Base({.a = a, .b = b}) does not work). Also, in my case, Base's copy constructor is deleted. Also, I am using C++ 11.
The solution I came up with uses the placement new operator to manually call the constructor of the Base class on the memory pointed to by this. To achieve this I also had to add a protected default constructor to my Base class. Are there any possible downsides to this approach and/or could anyone suggest a better method?
#include <iostream>
struct MyStruct
{
int a = 0;
int b = 1;
};
class Base
{
public:
Base(MyStruct str){
std::cout << "a: " << str.a << ", b: " << str.b << "\n";
}
Base(Base&&) = delete; // no copy constructor
protected:
Base(){ // dummy, does exactly nothing.
// it only exists to be called by
// the derived class's constructor
}
private:
int amember;
};
class Derived : public Base
{
public:
Derived(int a, int b)
{
MyStruct str;
str.a = a;
str.b = b;
new (this) Base(str);
}
private:
int anothermember;
};
int main()
{
MyStruct str;
str.a = 10;
str.b = 20;
Base b(str);
Derived d(10, 20);
return 0;
}
edit: added mention that Base cannot be copied, made explicit that Base::Base() does exactly nothing.
Use a helper function instead like
class Derived : public Base
{
public:
Derived(int a, int b) : Base(make_mystruct(a, b)), anothermember(some_value) {}
private:
int anothermember;
static MyStruct make_mystruct(int a, int b) { return MyStruct(a, b); }
};
I would just like to add that this could be a good opportunity to use IILE, Immediately Invoked Lambda Expression, if you for whatever reason don't want a named helper function:
class Derived : public Base
{
public:
Derived(int a, int b) : Base{[](){
MyStruct str;
str.a = a;
str.b = b;
return str; }()}
{}
};
The benefit is that you will not need to construct the class and it's members only once, since you do everything in the initialization list. If you in the future add non-trivial members to Base, you will be will not have to pay for double initialization.

Difference between initializing in initialization list and initializing in the constructor

I'm not certain if I have titled my question correctly, so feel free to correct me. I believe that:
Initializing in initialization list is equivalent to
int a = a;
Initializing in the constructor is equivalent to
int a; a = a;
But I still can't figure out the reason for the following output:
#include <iostream>
using namespace std;
class test
{
int a,b;
public:
/*
test(int a, int b): a(a), b(b) {} //OUTPUT: 3 2
test(int a, int b) { a = a; b = b;} //OUTPUT: -2 1972965730
test(int c, int d) { a = c; b = d;} //OUTPUT: 3 2
Hence it does work without this pointer. Unless the variable names are same
*/
void print() { cout<<a<<" "<<b<<"\n";}
};
int main()
{
test A(3,2);
A.print();
return 0;
}
EDITS:
As M.M pointed out: The equivalent of a(a) is this->a = a.
Worth a read: Why should I prefer to use member initialization list?
Two workarounds are:
test(int a, int b) { this->a = a; this->b = b;}
test(int a, int b) { test::a = a; test::b = b;}
test(int a, int b) { a = a; b = b;}
This is not correct. It does nothing to the data members. It should be
test(int a, int b) { this->a = a; this->b = b;}
In the initialization list the syntax is such that each variable name outside the parens () is a class member. What goes inside the parens is whatever happens to be in scope- be it a class member or a constructor parameter. A parameter will hide a class member.
So you can safely do:
class MyClass
{
int i;
MyClass(int i): i(i) {}
// ^-must be class member
};
And the compiler will correctly use the parameter from inside the parens to initialize the class member outside the parens.
What happens inside the parens is given the same scope as what happens inside the constructor body.
So:
class MyClass
{
int i;
MyClass(int i)
{
i = i; // BOTH of those are the parameter i
}
}
The parameter called i is hiding the class member called i so the class member never gets accessed in the constructor body.
You have to explicitly disambiguate it using this:
class MyClass
{
int i;
MyClass(int i)
{
// now we set class member i to parameter i
this->i = i;
}
}
All of that is taken care of for you in the syntax of the initializer list:
// v-this parameter is hiding the class member
MyClass(int i): i(i) {}
// ^-must be the class member
The initializer list is basically doing: this->i = i for you.
You should always initialize members in the initializer list if possible.
Try to replace
test(int a, int b) { a = a; b = b;} with test(int a, int b) { this->a = a; this->b = b;}
My compiler (msvc) produces desired result.
Every function introduces a new scope. When you define your constructor as
test(int a, int b) { a = a; b = b; }
you hide the class members. There is no way for compiler to know that the left a belongs to the class, and the right a is an argument.
When you declare the constructor as
test(int c, int d) { a = c; b = d; }
you don't have this problem, because there are no more naming clashes.
In the suggested fix, the same reasoning applies:
test(int a, int b) { this->a = a; this->b = b; }
You explicitly qualify that lhs a is a class member by using a this pointer, which is implicitly passed to every member function, including the constructor. However, this code is not equivalent to initialising with the initialisation list. You was correct in your question:
Initializing in initialization list is equivalent to
int a = a;
Initializing in the constructor is equivalent to
int a; a = a;
This would make a big difference if your member variable was some complex class. Without the initialisation list, you would first create an object using a default constructor and then copy-assign a new value to it, while if you used initialisation list, only copy-construction would happen.
Thus, you should always prefer using initialisation list:
test(int a, int b): a(a), b(b) {}

Why will C++ copy constructor fail?

#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A() { i=1; j=2;};
A (A &obj) { i= obj.i+100; j= obj.j+100;};
int i;
int j;
};
class B:public A
{
public:
B():A() {i=10; j=20; k=30;};
B(A &obj) { A::A(obj); k=10000; };//
int k;
};
int main()
{
A dog;
B mouse(dog);
cout<<mouse.i<<endl;
cout<<mouse.k<<endl;
return 0;
}
I try to write a copy constructor for the derived class that takes advantage of the copy constructor for the base class. I expect that mouse.i should be 101, but in fact the compiling result is 1. The value for mouse.k is 10000, which is expected. I was wondering what's wrong with my code.
In this constructor:
B(A &obj) { A::A(obj); k=10000; };
A::A(obj); does not initialise the base sub-object; instead, it creates a local object also called obj. It's equivalent to A::A obj;, which is equivalant to A obj;. [UPDATE: or possibly it does something else, or possibly it's ill-formed - in any event, it's wrong.]
You want to use an initialiser list:
B(A & obj) : A(obj), k(10000) {}
Also, you almost certainly want the constructor parameters to be A const &, to allow construction from constant objects or temporaries.
You must use the initialization list to call the parent's constructor (and you should do it for all other members also):
B(A const& obj) : A(obj), k(10000) {}
Additionally, when copying you do not modify the original object, so you should take a const reference to it. That will allow you to copy from constant objects (or through constant references), improving const-correctness.
You should initialize the base class like this:
B(A &obj):A(obj) { k=10000; }
(more on this at What are the rules for calling the superclass constructor?). And a side-note: use const for copy constructor arguments:
A (const A &obj) {...}
EDIT:
The preferred way to initialize instance members is through an initialization list, so your ctor will look like
B(A &obj):A(obj), k(10000) { }
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A()
{
i=1;
j=2;
}
A (A &obj)
{
i= obj.i+100;
j= obj.j+100;
}
int i;
int j;
};
class B:public A
{
public:
B():A() {i=10; j=20; k=30;}
B(A &obj)
:A(obj)
{
//A::A(obj);
k=10000;
}//
int k;
};
int main()
{
A dog;
B mouse(dog);
cout<<mouse.i<<endl;
cout<<mouse.k<<endl;
return 0;
}
This work for me
B(A &obj)
{
A::A(obj)
}
Is illegal on gcc compiler

C++ , Default constructor

When a constructor in a superclass receives arguments, it is no longer a default constructor, right? For example
class a {
public:
int a;
int b;
a(int c, int d){
cout<<"hello";
};
}
Now when I try to make a subclass, the program causes an error, it says "no default constructor is defined in the super class". How can I solve this problem? I know that if I remove the arguments, everything is going to be fine but I'm told not to do so in my C++ test. Please help me figure it out.
If your base class isn't default-constructible, or if you don't want to use the base class's default constructor, then you simply have to tell the derived class how to construct the base subobject:
struct b : a
{
b(int n) : a(n, 2*n) { }
// ^^^^^^^^^ <-- base class initializer, calls desired constructor
};
You normally deal with this with an initializer list:
#include <iostream>
class a {
public:
a(int c, int d) { std::cout << c << " " << d << "\n"; }
};
class b : public a {
public:
b() : a(1, 2) {}
};
int main() {
b x;
return 0;
}
You have to provide a constructor which takes no argument yourself.
a::a()
{
}
Once you provide any constructor for your class the compiler does not generate the implicit default constructor which takes no arguments. So if your code then needs a no arguments constructor you will have to provide it yourself.

C++ referring to an object being constructed

In C++ I have a reference to an object that wants to point back to its owner, but I can't set the pointer during the containing class' construction because its not done constructing. So I'm trying to do something like this:
class A {
public:
A() : b(this) {}
private:
B b;
};
class B {
public:
B(A* _a) : a(_a) {}
private:
A* a;
};
Is there a way to ensure B always gets initialized with an A* without A holding a pointer to B?
Thanks
Try this:
class A;
class B {
public:
B(A *_a) : a(_a) {};
private:
A* a;
};
class A {
public:
A() : b(this) {};
private:
B b;
};
Since B is contained completely in A, it must be declared first. It needs a pointer to A, so you have to forward-declare A before you declare B.
This code compiles under more-or-less current versions of g++.
In C++ I have a reference to an object that wants to point back to its owner, but I can't set the pointer during the containing class' construction because its not done constructing.
You can store the pointer alright.
What you can't do is to try to get to the members/methods of A through the pointer in the constructor of B, since the parent instance might not be fully initialized at the point:
#include <iostream>
class Y;
class X
{
Y* y;
public:
X(Y* y);
};
class Y
{
X x;
int n;
public:
Y(): x(this), n(42) {}
int get_n() const { return n; }
};
X::X(Y* p): y(p)
{
//Now this is illegal:
//as it is, the n member has not been initialized yet for parent
//and hence get_n will return garbage
std::cout << p->get_n() << '\n';
}
int main()
{
Y y;
}
If you were to switch around the members in Y, so n would get initialized first, the constructor of X would print 42, but that is too fragile to depend on.