The parameter type of the copy constructor in c++ must be a reference

  • 2020-04-02 01:14:14
  • OfStack

In C++, constructors, copy constructors, destructors, and assignment functions (assignment operator overloading) are among the most basic things to know. But if I ask you, "why do I have to use reference types for copy constructor arguments?" How would you answer this question? Perhaps you would answer in order to reduce a memory copy? To my shame, my first feeling was the same. But fortunately, when I thought about it, I found that the answer was not right.

The reason:
If the argument in the copy constructor is not a reference, that is, like CClass(const CClass c_class), then pass-by-value is passed, which calls the copy constructor of the class, causing infinite recursive calls to the copy constructor. So the argument to the copy constructor must be a reference.

To be clear, a pass pointer is also a pass value, and if the copy constructor above is written as CClass(const CClass* c_class), it won't work either. In fact, all passing methods are passing values except passing references.
Let's start with a small example :(test yourself to see what the output of this program is.)


#include<iostream>
using namespace std;
class CExample
{
private:
 int m_nTest;
public:
 CExample(int x) : m_nTest(x)      //Parameter constructor
 { 
  cout << "constructor with argument"<<endl;
 }
 //Copy constructor, const in arguments is not strictly required, but reference notation is
 CExample(const CExample & ex)     //Copy constructor
 {
  m_nTest = ex.m_nTest;
  cout << "copy constructor"<<endl;
 }
 CExample& operator = (const CExample &ex)   //Assignment function (assignment operator overload)
 { 
  cout << "assignment operator"<<endl;
  m_nTest = ex.m_nTest;
  return *this;
 }
 void myTestFunc(CExample ex)
 {
 }
};
int main(void)
{
 CExample aaa(2);
 CExample bbb(3);
 bbb = aaa;
 CExample ccc = aaa;
 bbb.myTestFunc(aaa);
 return 0; 
}

If you can see at a glance that this is the result, congratulations, you can stand up and wiggle your ass, no need to look down.
If there is an error between your result and the output, please read it modestly.
The first output: The constructor with argument           / / CExample aaa (2);
If you don't understand, find someone to drag you out and beat you up, and then shout "I'm brother two, I'm brother two..."
The second output: The constructor with argument         / / CExample BBB (3);
The analysis is the same as the first one
The third output: The assignment operator                               / / BBB = aaa;
The fourth output: The copy constructor                                           // CExample CCC = aaa;
These two have to be put together. I'm sure someone will ask why the two don't agree. The reason is, the BBB object is already instantiated, it doesn't need to be constructed, it just assigns aaa to BBB, it just calls the assign function, it's that simple, if you don't understand, hit the wall! But the CCC hasn't been instantiated yet, so it calls the copy constructor to build the CCC, not the assignment function, and if I don't get it, I'm going to hit the wall!!
Fifth output: The copy constructor                                           //   BBB. MyTestFunc (aaa);
Actually, aaa is passed to bbb.myTestFunc(CExample ex) as a parameter, that is, CExample ex = aaa; It's the same as the fourth one, so copy the constructor instead of the assignment function. If you still don't understand, my head is bleeding. Don't let me hit it again.
With this example, let's examine why copying constructor arguments can only use reference types.
Look at the fourth output: The copy constructor                                           // CExample CCC = aaa;
Construct CCC, which is essentially CCC.CExample(aaa); If the copy constructor argument is not a reference type, we will make ccc.cexample (aaa) aaa and pass it to ccc.cexample (CExample ex), CExample ex = aaa, because ex is not initialized, so CExample ex = aaa continues to call the copy constructor, and then we will construct ex, which is ex.CExample(aaa), So there's going to be another aaa passed to CExample, which is equal to aaa; Then it's going to trigger the copy constructor again, and it's going to recurse forever.
So by going around that big of a curve, you want to show that copying constructor arguments using reference types is not to reduce a memory copy, but to avoid unbounded recursion of the copy constructor.

Incidentally, the copy constructor is called in the following cases:
A, explicitly or implicitly initialize another object with an object of the same type. In the example above, d is initialized with object c;
B, pass it to a function as an argument. In CClass(const CClass c_class), the copy constructor of CClass is called.
C. When an object is returned in the body of a function, the copy constructor of the return value type is also called;
D. When initializing elements in the sequence container. Such as the vector < The string > Svec (5), the default constructor of string and the copy constructor are both called;
E. When an array element is initialized as a list. String a[] = {string(" hello "), string(" world ")}; The copy constructor of the string is called.

If no constructor is explicitly declared, the compiler composes a default constructor for a class. If a constructor is declared in a class, the compiler is prevented from synthesizing the default constructor for that class. Unlike constructors, the compiler always synthesizes a copy constructor for us, even if other constructors are defined (but no copy constructor is defined).

In addition, the return value of the function is not a reference there is a big difference, return is not a reference, just a simple object, this time you need to call the copy constructor, otherwise, if the reference does not need to call the copy constructor.


#include<iostream>
using namespace std;
class A
{
private:
 int m_nTest;
public:
 A()
 {
 }
 A(const A& other)    //Constructor overload
 {
  m_nTest = other.m_nTest;
  cout << "copy constructor"<<endl;  
 }
 A & operator =(const A& other)
 {
  if(this != &other)
  {
   m_nTest = other.m_nTest;
   cout<<"Copy Assign"<<endl;
  }
  return *this;
 }
};
A fun(A &x)
{
 return x;     //When a reference is not returned, the copy constructor needs to be called
}
int main(void)
{
 A test;
 fun(test);
 system("pause");
 return 0;
}

Share a written test question, compile and run the C++ code in the figure below. What is the result? (A) compilation error; (B) successful compilation, runtime program crash; (C) the compilation runs normally, output 10. Please choose the right answer and analyze the reason.

class A
{
private:
 int value;
public:
 A(int n)
 {
  value = n;
 }
 A(A other)
 {
  value = other.value;
 }
 void Print()
 {
  cout<<value<<endl;
 }
};
int main(void)
{
 A a = 10;
 A b = a;
 b.Print();
 return 0;
}

Answer: compilation error. The parameter passed in the copy constructor is an instance of A. Since it is a pass value, copy the parameter to the real participant to call the copy constructor. So if the copy constructor is allowed to pass values, there is an endless recursion and stack overflow. Therefore, the C++ standard does not allow the copy constructor to pass value arguments, but must be passed references or constant references. In both Visual Studio and GCC, compilation errors occur.


Related articles: