Constructor and Destructor in C++

Can we make copy constructor private?
Yes, a copy constructor can be made private. When we make a copy constructor private in a class, objects of that class become non-copyable. This is particularly useful when our class has pointers or dynamically allocated resources. In such situations, we can either write our own copy constructor like above String example or make a private copy constructor so that users get compiler errors rather than surprises at runtime.

Why argument to a copy constructor must be passed as a reference?
A copy constructor is called when an object is passed by value. Copy constructor itself is a function. So if we pass an argument by value in a copy constructor, a call to copy constructor would be made to call copy constructor which becomes a non-terminating chain of calls. Therefore compiler doesn’t allow parameters to be passed by value.

 

Can there be more than one destructor in a class?
No, there can only one destructor in a class with classname preceded by ~, no parameters and no return type.

When do we need to write a user-defined destructor?
If we do not write our own destructor in class, compiler creates a default destructor for us. The default destructor works fine unless we have dynamically allocated memory or pointer in class. When a class contains a pointer to memory allocated in class, we should write a destructor to release memory before the class instance is destroyed. This must be done to avoid memory leak.

Can a destructor be virtual?
Yes, In fact, it is always a good idea to make destructors virtual in base class when we have a virtual function. See virtual destructor for more details.

You may like to take a quiz on destructors.

Objects are always destroyed in reverse order of their creation. The reason for reverse order is, an object created later may use the previously created object. For example, consider the following code snippet.

Does C++ compiler create default constructor when we write our own?

In C++, compiler by default creates default constructor for every class. But, if we define our own constructor, compiler doesn’t create the default constructor.

A a;
B b(a);

In the above code, the object ‘b’ (which is created after ‘a’), may use some members of ‘a’ internally. So destruction of ‘a’ before ‘b’ may create problems. Therefore, object ‘b’ must be destroyed before ‘a’.

 

Whenever we want to control destruction of objects of a class, we make the destructor private. For dynamically created objects, it may happen that you pass a pointer to the object to a function and the function deletes the object. If the object is referred after the function call, the reference will become dangling. 

Initialization of data members

In C++, class variables are initialized in the same order as they appear in the class declaration.

Consider the below code.

#include<iostream> 

using namespace std; 

class Test { 
private:	 
	int y; 
	int x;	 
public: 
	Test() : x(10), y(x + 10) {} 
	void print(); 
}; 

void Test::print() 
{ 
cout<<"x = "<<x<<" y = "<<y; 
} 

int main() 
{ 
	Test t; 
	t.print(); 
	getchar(); 
	return 0;	 
} 

 The program prints correct value of x, but some garbage value for y, because y is initialized before x as it appears before in the class declaration. 

Use of explicit keyword in C++

Predict the output of following C++ program.

#include <iostream> 

using namespace std; 

class Complex 
{ 
private: 
	double real; 
	double imag; 

public: 
	// Default constructor 
	Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} 

	// A method to compare two Complex numbers 
	bool operator == (Complex rhs) { 
	return (real == rhs.real && imag == rhs.imag)? true : false; 
	} 
}; 

int main() 
{ 
	// a Complex object 
	Complex com1(3.0, 0.0); 

	if (com1 == 3.0) 
	cout << "Same"; 
	else
	cout << "Not Same"; 
	return 0; 
} 

  

Output: The program compiles fine and produces following output.

Same 

As discussed in this GFact, in C++, if a class has a constructor which can be called with a single argument, then this constructor becomes conversion constructor because such a constructor allows conversion of the single argument to the class being constructed.
We can avoid such implicit conversions as these may lead to unexpected results. We can make the constructor explicit with the help of explicit keyword. For example, if we try the following program that uses explicit keyword with constructor, we get compilation error.

#include <iostream> 

using namespace std; 

class Complex 
{ 
private: 
	double real; 
	double imag; 

public: 
	// Default constructor 
	explicit Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} 

	// A method to compare two Complex numbers 
	bool operator== (Complex rhs) { 
	return (real == rhs.real && imag == rhs.imag)? true : false; 
	} 
}; 

int main() 
{ 
	// a Complex object 
	Complex com1(3.0, 0.0); 

	if (com1 == 3.0) 
	cout << "Same"; 
	else
	cout << "Not Same"; 
	return 0; 
} 

  

Output: Compiler Error

no match for 'operator==' in 'com1 == 3.0e+0'

We can still typecast the double values to Complex, but now we have to explicitly typecast it. For example, the following program works fine.

#include <iostream> 

using namespace std; 

class Complex 
{ 
private: 
	double real; 
	double imag; 

public: 
	// Default constructor 
	explicit Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} 

	// A method to compare two Complex numbers 
	bool operator== (Complex rhs) { 
	return (real == rhs.real && imag == rhs.imag)? true : false; 
	} 
}; 

int main() 
{ 
	// a Complex object 
	Complex com1(3.0, 0.0); 

	if (com1 == (Complex)3.0) 
	cout << "Same"; 
	else
	cout << "Not Same"; 
	return 0; 
} 

  

Output: The program compiles fine and produces following output.

Same 

When do we use Initializer List in C++?

Initializer List is used to initialize data members of a class. The list of members to be initialized is indicated with constructor as a comma separated list followed by a colon. Following is an example that uses initializer list to initialize x and y of Point class.

#include<iostream> 
using namespace std; 

class Point { 
private: 
	int x; 
	int y; 
public: 
	Point(int i = 0, int j = 0):x(i), y(j) {} 
	/* The above use of Initializer list is optional as the 
		constructor can also be written as: 
		Point(int i = 0, int j = 0) { 
			x = i; 
			y = j; 
		} 
	*/	
	
	int getX() const {return x;} 
	int getY() const {return y;} 
}; 

int main() { 
Point t1(10, 15); 
cout<<"x = "<<t1.getX()<<", "; 
cout<<"y = "<<t1.getY(); 
return 0; 
} 

/* OUTPUT: 
x = 10, y = 15 
*/

  

#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; 
} 

/* OUTPUT: 
10 
*/

  

The above code is just an example for syntax of Initializer list. In the above code, x and y can also be easily initialed inside the constructor. But there are situations where initialization of data members inside constructor doesn’t work and Initializer List must be used. Following are such cases:

1) For initialization of non-static const data members:
const data members must be initialized using Initializer List. In the following example, “t” is a const data member of Test class and is initialized using Initializer List.

 
class CExample
{
public:
	CExample():m_a(1),m_b(2){/*m_a = 1; compile error*/}
	CExample(const CExample&c):m_a(1){/*m_a = 1; compile error*/}
	~CExample(){}
private:
	const int m_a;
	int m_b;
};

总结:

1、类的const成员变量必须在构造函数的参数初始化列表中进行初始化。

2、构造函数内部,不能对const成员赋值,编译器直接报错。

3、构造函数列表初始化执行顺序与成员变量在类中声明相同,与初始化列表中语句书写先后无关。

4、初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。因此,由于常量只能初始化不能赋值,所以常量成员必须使用初始化列表;

2) For initialization of reference members:
Reference members must be initialized using Initializer List. In the following example, “t” is a reference member of Test class and is initialized using Initializer List.

// Initialization of reference data 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; 
} 
/* OUTPUT: 
	20 
	30 
*/

  引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表中;

  3) For initialization of member objects which do not have default constructor:
In the following example, an object “a” of class “A” is data member of class “B”, and “A” doesn’t have default constructor. Initializer List must be used to initialize “a”.
#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; 
} 
/* OUTPUT: 
	A's Constructor called: Value of i: 10 
	B's Constructor called 
*/

  3) For initialization of member objects which do not have default constructor:
In the following example, an object “a” of class “A” is data member of class “B”, and “A” doesn’t have default constructor. Initializer List must be used to initialize “a”.

#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; 
} 
/* OUTPUT: 
	A's Constructor called: Value of i: 10 
	B's Constructor called 
*/

  6) For Performance reasons:
It is better to initialize all class variables in Initializer List instead of assigning values inside body. Consider the following example:

// Without Initializer List 
class MyClass { 
	Type variable; 
public: 
	MyClass(Type a) { // Assume that Type is an already 
					// declared class and it has appropriate 
					// constructors and operators 
	variable = a; 
	} 
}; 

 

Here compiler follows following steps to create an object of type MyClass
1. Type’s constructor is called first for “a”.
2. The assignment operator of “Type” is called inside body of MyClass() constructor to assign

    variable = a; 

3. And then finally destructor of “Type” is called for “a” since it goes out of scope.

Now consider the same code with MyClass() constructor with Initializer List

// With Initializer List 
class MyClass { 
	Type variable; 
public: 
	MyClass(Type a):variable(a) { // Assume that Type is an already 
					// declared class and it has appropriate 
					// constructors and operators 
	} 
}; 

  

With the Initializer List, following steps are followed by compiler:
1. Copy constructor of “Type” class is called to initialize : variable(a). The arguments in initializer list are used to copy construct “variable” directly.
2. Destructor of “Type” is called for “a” since it goes out of scope.

As we can see from this example if we use assignment inside constructor body there are three function calls: constructor + destructor + one addition assignment operator call. And if we use Initializer List there are only two function calls: copy constructor + destructor call. See this post for a running example on this point.

This assignment penalty will be much more in “real” applications where there will be many such variables. Thanks to ptr for adding this point.

 

Output of C++ Program | Set 13

Predict the output of following C++ program.

#include<iostream> 
using namespace std; 

class A 
{ 
	// data members of A 
public: 
	A ()		 { cout << "\n A's constructor"; /* Initialize data members */ } 
	A (const A &a) { cout << "\n A's Copy constructor"; /* copy data members */} 
	A& operator= (const A &a) // Assignemt Operator 
	{ 
		// Handle self-assignment: 
		if(this == &a) return *this; 

		// Copy data members 
		cout << "\n A's Assignment Operator"; return *this; 
	} 
}; 

class B 
{ 
	A a; 
	// Other members of B 
public: 
	B(A &a) { this->a = a; cout << "\n B's constructor"; } 
}; 

int main() 
{ 
	A a1; 
	B b(a1); 
	return 0; 
} 

  

Output:

 A's constructor
 A's constructor
 A's Assignment Operator
 B's constructor

The first line of output is printed by the statement “A a1;” in main().
The second line is printed when B’s member ‘a’ is initialized. This is important.
The third line is printed by the statement “this->a = a;” in B’s constructor.
The fourth line is printed by cout statement in B’s constructor.

If we take a closer look at the above code, the constructor of class B is not efficient as member ‘a’ is first constructed with default constructor, and then the values from the parameter are copied using assignment operator. It may be a concern when class A is big, which generally is the case with many practical classes. See the following optimized code.

#include<iostream> 
using namespace std; 

class A 
{ 
	// data members of A 
public: 
	A()		 { cout << "\n A's constructor"; /* Initialize data members */ } 
	A(const A &a) { cout << "\n A's Copy constructor"; /* Copy data members */ } 
	A& operator= (const A &a) // Assignemt Operator 
	{ 
		// Handle self-assignment: 
		if(this == &a) return *this; 

		// Copy data members 
		cout << "\n A's Assignment Operator"; return *this; 
	} 
}; 

class B 
{ 
	A a; 
	// Other members of B 
public: 
	B(A &a):a(a) { cout << "\n B's constructor"; } 
}; 

int main() 
{ 
	A a; 
	B b(a); 
	return 0; 
} 

 

Output:

 A's constructor
 A's Copy constructor
 B's constructor

The constructor of class B now uses initializer list to initialize its member ‘a’. When Initializer list is used, the member ‘a’ of class B is initialized directly from the parameter. So a call to A’s constructor is reduced.
In general, it is a good idea to use Initializer List to initialize all members of a class, because it saves one extra assignment of members. 

 

Why copy constructor argument should be const in C++?

When we create our own copy constructor, we pass an object by reference and we generally pass it as a const reference. 
One reason for passing const reference is, we should use const in C++ wherever possible so that objects are not accidentally modified. This is one good reason for passing reference as const, but there is more to it. For example, predict the output of following C++ program. Assume that copy elision is not done by compiler.

#include<iostream> 
using namespace std; 

class Test 
{ 
/* Class data members */
public: 
Test(Test &t) { /* Copy data members from t*/} 
Test()	 { /* Initialize data members */ } 
}; 

Test fun() 
{ 
	cout << "fun() Called\n"; 
	Test t; 
	return t; 
} 

int main() 
{ 
	Test t1; 
	Test t2 = fun(); 
	return 0; 
} 

  

Output:

 Compiler Error in line "Test t2 = fun();" 

The program looks fine at first look, but it has compiler error. If we add const in copy constructor, the program works fine, i.e., we change copy constructor to following.

Test(const Test &t) { cout << "Copy Constructor Called\n"; } 

  Or if we change the line “Test t2 = fun();” to following two lines, then also the program works fine.

Test t2; 
t2 = fun(); 

  The function fun() returns by value. So the compiler creates a temporary object which is copied to t2 using copy constructor in the original program (The temporary object is passed as an argument to copy constructor). The reason for compiler error is, compiler created temporary objects cannot be bound to non-const references and the original program tries to do that. It doesn’t make sense to modify compiler created temporary objects as they can die any moment.

转载于:https://www.cnblogs.com/EMH899/p/10805469.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值