[C++参考]拷贝构造函数的参数必须是引用类型

在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识。在effective C++中说过这么一点:拷贝构造函数的参数必须是引用类型的。但是为什么呢?

拷贝构造函数的参数必须是引用类型的

如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。

需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),是可以运行的。但是参数变指针后,已经不是拷贝构造函数了,已经变成了普通的构造函数,程序不会在需要拷贝构造函数的时候自动调用它。

看下面的代码:

#include<iostream>
using namespace std;

class CExample
{
private:
	int m_nTest;

public:
	CExample(int x) : m_nTest(x)      //带参数构造函数
	{
		cout << "constructor with argument"<<endl;
	}

	// 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的
	CExample(const CExample & ex)     //拷贝构造函数
	{
		m_nTest = ex.m_nTest;
		cout << "copy constructor"<<endl;
	}

	CExample& operator = (const CExample &ex)   //赋值函数(赋值运算符重载)
	{
		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;
}

  这个例子的输出结果:

constructor with argument        // CExample aaa(2);
constructor with argument        // CExample bbb(3);
assignment operator              // bbb = aaa;
copy constructor                 // CExample ccc = aaa;
copy constructor                 //  bbb.myTestFunc(aaa);

  分析:第一个和第二个没有什么特别的地方,就是普通的构造函数。第三个和第四个为什么结果不一样:

原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数。但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数。

第五个:实际上是aaa作为参数传递给bbb.myTestFunc(CExample ex), 即CExample ex = aaa;和第四个一致的, 所以还是拷贝构造函数,而不是赋值函数。

通过这个例子, 我们来分析一下为什么拷贝构造函数的参数只能使用引用类型。

看第四个输出: copy constructor                      // CExample ccc = aaa;

构造ccc,实质上是ccc.CExample(aaa); 我们假如拷贝构造函数参数不是引用类型的话, 那么将使得 ccc.CExample(aaa)变成aaa传值给ccc.CExample(CExample ex),即CExample ex = aaa,因为 ex 没有被初始化, 所以 CExample ex = aaa 继续调用拷贝构造函数,接下来的是构造ex,也就是 ex.CExample(aaa),必然又会有aaa传给CExample(CExample ex), 即 CExample ex = aaa;那么又会触发拷贝构造函数,就这下永远的递归下去。

在C++中,调用拷贝构造函数有三种情况:

1.一个对象作为函数参数,以值传递的方式传入函数体

2.一个对象作为函数返回值,以值传递的方式从函数返回

3.一个对象用于给另外一个对象进行初始化(创建对象时初始化).

如果在没有显式声明构造函数的情况下,编译器都会为一个类合成一个缺省的构造函数。如果在一个类中声明了一个构造函数,那么就会阻止编译器为该类合成缺省的构造函数。但是定义了其他构造函数(但没有定义拷贝构造函数),编译器总是会为我们合成一个拷贝构造函数,也就是说自定义的构造函数不会阻止默认的拷贝构造函数。

另外函数的返回值是不是引用也有很大的区别,返回的不是引用的时候,只是一个简单的对象,此时需要调用拷贝构造函数,否则,如果是引用的话就不需要调用拷贝构造函数。

#include<iostream>
using namespace std;

class A
{
private:
	int m_nTest;
public:
	A()
	{
	}
	A(const A& other)    //构造函数重载
	{
		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;     //返回的不是引用的时候,需要调用拷贝构造函数
}

int main(void)
{
	A test;
	fun(test);
	system("pause");
	return 0;
}

  

什么时候需要自定义拷贝构造函数和赋值符函数.

简单的规则:如果需要定义一个非空的析构函数,那么,通常情况下也需要定义一个拷贝构造函数和赋值符函数.

通常的原则是:

1.对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;

2.在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符,即提供赋值符函数.

当我们知道需要自定义拷贝构造函数和赋值符函数时,就得考虑如何良好的实现它们.

当自定义copying函数(包含拷贝构造函数和赋值符函数)时,需要确保以下两点:

1.复制所有的local成员变量

2.调用所有base classes内的适当的copying函数,完成基类的copying.

下面是一个具体的例子:

class Customer {
public:
    ...
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
    ...
private:
    std::string name;
};

Customer::Customer(const Customer& rhs):name(rhs.name)
{
    cout << "Customer copy constructor" << endl;
}
Customer& Customer::operator=(const Customer& rhs)
{
    cout << "Customer copy assignment operator" << endl;
    name = rhs.name;                    //疑惑,为什么在copying函数里可以通过对象调用私有变量?
    return *this;
}

class PriorityCustomer:public Customer {
public:
    ...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs);
    ...
private:
    int priority;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs),priority(rhs.priority)
{
    cout << "PriorityCustomer copy constructor" << endl;
}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
    cout << "PriorityCustomer copy assignment operator" << endl;
    Customer::operator=(rhs);                //对base class成分进行赋值动作
    priority = rhs.priority;
    return *this;
}

  上面代码中,通过对象直接访问private的成员变量,似乎违背了对象的封装,具体的分析和理解参考下面的链接:

C++私有成员变量的理解

时间: 2024-11-05 18:31:55

[C++参考]拷贝构造函数的参数必须是引用类型的相关文章

拷贝构造函数的参数为什么必须使用引用类型(避免无限递归拷贝,但其实编译器已经强制要求了)

在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识. 但是如果我问你“拷贝构造函数的参数为什么必须使用引用类型?”这个问题, 你会怎么回答? 或许你会回答为了减少一次内存拷贝? 很惭愧的是,我的第一感觉也是这么回答.不好还好,我有理性这个好品质.思索一下以后,发现这个答案是不对的.让我来撕开(有点暴力,但是我喜欢,嘿嘿--龇牙)那件隐藏在真理外的小裤衩,让它袒露在“登徒子”们的眼前吧. 先从一个小例子开始:(自己测试一下自己看看这个程序的输出是

原来... 拷贝构造函数的参数为什么必须使用引用类型

原来... 拷贝构造函数的参数为什么必须使用引用类型 转自:http://blog.csdn.net/tunsanty/article/details/4264738 在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识. 但是如果我问你“拷贝构造函数的参数为什么必须使用引用类型?”这个问题, 你会怎么回答? 或许你会回答为了减少一次内存拷贝? 很惭愧的是,我的第一感觉也是这么回答.不好还好,我有理性这个好品质.思索一下以后,发现这个答案是不对的

拷贝构造函数的参数为什么必须是引用?

拷贝构造函数的参数为什么必须是const引用? 先看如下代码: class CExample { private: int a; public: //构造函数 CExample(int b) { a = b;} //拷贝构造函数 CExample(const CExample& C) { a = C.a; } }; int main() { CExample A(100); CExample B(A); return 0; } 1 首先讨论为什么必须要是引用? 如果这里改成 : CExample

拷贝构造函数的参数类型必须是引用

在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识. 但是如果我问你“拷贝构造函数的参数为什么必须使用引用类型?”这个问题, 你会怎么回答? 或许你会回答为了减少一次内存拷贝? 很惭愧的是,我的第一感觉也是这么回答.不过还好,我思索一下以后,发现这个答案是不对的. 原因:       如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),

C++学习之拷贝构造函数

嘛是拷贝构造函数? 如果一个构造函数的第一个参数是’自身类‘ ‘类型’的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数.如: [代码1] 1 2 3 4 5 6 class A{ public:    A();         //默认构造函数    A(const A&); //拷贝构造函数:const可省略,但此参数几乎总是一个const的引用!    //其他内容 } 拷贝构造函数会被隐式地使用,所以不应该定义成explicit的(explicit构造函数将限制隐式转换,只能以

C++ 为什么拷贝构造函数参数必须为引用?赋值构造函数参数也必须为引用吗?

之前写拷贝构造函数的时候,以为参数为引用,不为值传递,仅仅是为了减少一次内存拷贝.然而今天看到一篇文章发现自己对拷贝构造的参数理解有误. 参数为引用,不为值传递是为了防止拷贝构造函数的无限递归,最终导致栈溢出. 下面来看一个例子: class test { public: test() { cout << "constructor with argument\n"; } ~test() { } test(test& t) { cout << "

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

C++构造函数 &amp; 拷贝构造函数 &amp; 派生类的构造函数 &amp; 虚继承的构造函数

构造函数 ,是一种特殊的方法 .主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 .特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载.(摘自百度百科构造函数). 一.最基本的构造函数 1 class Base 2 { 3 public: 4 Base(int var) : m_Var(var) 5 { 6 } 7 private: 8 int m_Var; 9 }; 以上构造函数的执行过程:

深入探索C++对象象模型--拷贝构造函数 &amp;&amp;多重继承 虚拟继承 内存分布

拷贝构造函数 如果没有定义拷贝构造函数,那么编译器会自动生成一个拷贝构造函数,但是这个拷贝构造函数是有一定限度的. 一般来说这个拷贝构造函数是按照位直接拷贝的,但是在有些情况下这种初始化是有问题的,在特殊的四种情况下是有问题的,在有问题的情况下,可以举例说明. 如果一个有多态性质的对象,子类赋值给父类,调用了拷贝构造函数,这个时候就需要给父类的虚拟函数表重新分配,使得虚拟函数表和子类不是同一个,这样bitwist就不能有效 对于在函数参数中调用拷贝构造函数,参数是实参的一根拷贝,对于函数的返回值