C++的拷贝构造函数

From Wikipedia, the free encyclopedia

Jump to: navigation, search
<!-- start content -->

A copy constructor is a special constructor in the C++ programming language used to create a new object as a copy of an existing object. This constructor takes a single argument: a reference to the object to be copied.

Normally the compiler automatically creates a copy constructor for each class (known as an implicit copy constructor) but for special cases the programmer creates the copy constructor, known as an explicit copy constructor. In such cases, the compiler doesn't create one.

A copy constructor is generally needed when an object owns pointers or non-shareable references, such as to a file, in which case a destructor and an assignment operator should also be written (see Rule of three).

Contents

[hide]
<script type="text/javascript"> //<![CDATA[ if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } //]]> </script>

[edit] Definition

Copying of objects is achieved by the use of a copy constructor and a copy assignment operator. A copy constructor has as its first parameter a (possibly const or volatile) reference to its own class type. It can have more arguments, but the rest must have default values associated with them.[1] The following would be valid copy constructors for class X:

X(X const&);
X(X&);
X(X const volatile&);
X(X volatile&);
X(X const&, int = 10);
X(X const&, double = 1.0, int = 40);

The first one should be used if a good reason to use one of the others does not exist. One of the differences between the first and the second is that temporaries can be copied with the first. For example:

X a = X();     // valid if you have X(X const&) but not valid if you have X(X&)
// because the second can't hold a temporary

Another difference between them is the obvious:

X const a;
X b = a; // valid if you have X(X const&) but not valid if you have X(X&)
// because the second wants a non-const X&

The X& form of the copy constructor is used when it is necessary to modify the copied object. This is very rare but it can be seen used in the standard library's std::auto_ptr. A reference must be provided:

X a;
X b = a; // valid if any of the copy constructors is defined
// since you're passing in a reference

The following are invalid copy constructors (or regular constructors):

X(X);
X(X const);

because the call to those constructors would require a copy as well, which would result in an infinitely recursive call.

There are four instances when a copy constructor is called:

  1. When an object is returned by value
  2. When an object is passed (into a function) by value as an argument
  3. When an object is constructed based on another object (of the same class)
  4. When the compiler generates a temporary object (as in 1 and 2 above; as in explicit casting, etc...)

[edit] Operation

An object can be assigned value using one of the two techniques:

  • Explicit assignment in an expression
  • Initialization

[edit] Explicit assignment in an expression

Object A;
Object B;
A = B; // translates as Object::operator=(const Object&), thus A.operator=(B) is called

[edit] Initialization

An object can be initialized by any one of the following ways.

a. Through declaration

Object B = A; // translates as Object::Object(const Object&)

b. Through function arguments

type function (Object a);

c. Through function return value

Object a = function();

The copy constructor is used only in latter case (initializations) and doesn't apply to assignments where the assignment operator is used instead.

The implicit copy constructor of a class calls base copy constructors and copies its members by means appropriate to their type. If it is a class type, the copy constructor is called. If it is a scalar type, the built-in assignment operator is used. Finally, if it is an array, each element is copied in the manner appropriate to its type.[2]

By using an explicit copy constructor the programmer can define the behavior to be performed when an object is copied.

[edit] Examples

These examples illustrate how copy constructors work and why they are required sometimes.

[edit] Implicit copy constructor

Let us consider the following example.

#include <iostream>

class Person
{
public:

int age;

Person(int age)
: age(age) {}
};

int main()
{
Person timmy(10);
Person sally(15);

Person timmy_clone = timmy;

std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;

timmy.age = 23;

std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;
}

Output

10 15 10
23 15 10

As expected, timmy has been copied to the new object, timmy_clone. While timmy's age was changed, timmy_clone's age remained the same. This is because they are totally different objects.

The compiler has generated a copy constructor for us, and it could be written like this:

Person(Person const& copy)
: age(copy.age) {}

So, when do we really need an explicit copy constructor? The next section will explore that question.

[edit] Explicit copy constructor

Now, consider a very simple dynamic array class like the following:

#include <iostream>

class Array
{
public:
int size;
int* data;

Array(int size)
: size(size), data(new int[size]) {}

~Array()
{
delete[] data;
}
};

int main()
{
Array first(20);
first.data[0] = 25;

{
Array copy = first;

std::cout << first.data[0] << " " << copy.data[0] << std::endl;

} // (1)

first.data[0] = 10; // (2)
}

Output

25 25
Segmentation fault

Since we didn't specify a copy constructor, the compiler generated one for us. The generated constructor would look something like:

Array(Array const& copy)
: size(copy.size), data(copy.data) {}

The problem with this constructor is that it performs a shallow copy of the data pointer. It only copies the address of the original data member; this means they both share a pointer to the same chunk of memory, which is not what we want. When the program reaches line (1), copy's destructor gets called (because objects on the stack are destroyed automatically when their scope ends). Array's destructor deletes the data array of the original, therefore when it deleted copy's data, because they share the same pointer, it also deleted first's data. Line (2) now accesses invalid data and writes to it! This produces the famous segmentation fault.

If we write our own copy constructor that performs a deep copy then this problem goes away.

Array(Array const& copy)
: size(copy.size), data(new int[copy.size])
{
std::copy(copy.data, copy.data + copy.size, data); // #include <algorithm> for std::copy
}

Here, we are creating a new int array and copying the contents to it. Now, copy's destructor only deletes its data and not first's data as well. Line (2) will not produce a segmentation fault anymore.

Instead of doing a deep copy right away, there are some optimization strategies that can be used. These allow you to safely share the same data between several objects, thus saving space. The copy on write strategy makes a copy of the data only when it is written to. Reference counting keeps the count of how many objects are referencing the data, and will delete it only when this count reaches zero (e.g boost::shared_ptr).

[edit] Copy constructors and templates

Contrary to expectations, a template copy constructor is not an explicit copy constructor. Thus it is not enough to just have:

template <typename A> Array::Array(const A& copy)
: size(copy.size()), data(new int[copy.size()])
{
std::copy(copy.begin(),copy.end(),data);
}

(Note that the type of A can be Array.) An explicit, non-template, copy constructor must also be provided for construction of Array from Array.

<think>我们正在讨论C++中的拷贝构造函数。根据用户要求,需要提供示例和解释。同时,我们需要参考提供的引用内容,特别是引用[1]中给出了一个自定义拷贝构造函数的例子。目标:1.解释拷贝构造函数的概念2.提供示例代码(参考引用[1]的例子,但可以适当调整使其更清晰)3.说明拷贝构造函数的调用时机4.区分拷贝构造函数和赋值操作符5.提及深拷贝与浅拷贝(引用[1]中提到)另外,引用[2]和[4]提到了移动语义,但用户当前问题集中在拷贝构造函数,因此我们主要关注拷贝,但可以简要提及移动以作对比(如果相关)。步骤:1.定义:拷贝构造函数是一种特殊的构造函数,用于创建一个新对象作为现有对象的副本。2.语法:通常是`ClassName(constClassName&other)`3.调用时机:当用同类型的另一个对象初始化新对象时。例如:-直接初始化:`Studentt(s);`-拷贝初始化:`Studentk=s;`(注意这里不是赋值,而是初始化)-传递对象给函数(按值传递)-从函数返回对象(按值返回)-用另一个对象初始化数组元素等4.注意:赋值操作符(operator=)是在已经存在的对象被赋予新值时调用,而不是初始化。如引用[1]中`m=s;`调用的是赋值操作符。我们将编写一个示例,展示拷贝构造函数的使用,并在注释中说明输出结果。同时,我们将解释深拷贝和浅拷贝的概念,因为自定义拷贝构造函数通常用于实现深拷贝。示例代码(基于引用[1]修改,使其更通用):```cpp#include<iostream>usingnamespacestd;classStudent{public://普通构造函数Student(intid):id_(id){}//默认构造函数(可能不需要,但为了示例中m的创建而提供)Student():id_(0){}//自定义拷贝构造函数Student(constStudent&other){//这里我们自定义拷贝行为:将id取负作为新对象的id(仅用于演示,实际中可能用于深拷贝)id_=other.id_*(-1);cout<<"Copyconstructorcalled.Originalid:"<<other.id_<<",newid:"<<id_<<endl;}//赋值操作符(这里使用默认的,但为了说明区别,我们也可以自定义)Student&operator=(constStudent&other){//注意:赋值操作符返回自身引用id_=other.id_;//这里直接复制id,没有取负(与拷贝构造函数区别)cout<<"Assignmentoperatorcalled.Assignedid:"<<id_<<endl;return*this;}//打印idvoidprint()const{cout<<"ID:"<<id_<<endl;}private:intid_;};intmain(){Students1(1001);//调用普通构造函数s1.print();//输出:ID:1001Students2(s1);//调用拷贝构造函数s2.print();//输出:ID:-1001Students3=s1;//调用拷贝构造函数(注意:这是初始化,不是赋值)s3.print();//输出:ID:-1001Students4;//调用默认构造函数s4.print();//输出:ID:0s4=s1;//调用赋值操作符s4.print();//输出:ID:1001return0;}```解释:-在拷贝构造函数中,我们故意将id取负,以突出拷贝构造函数的调用和自定义行为。-在赋值操作符中,我们直接复制id(没有取负),以区别于拷贝构造函数。-注意`s4=s1;`是赋值操作,因为s4已经存在。深拷贝与浅拷贝:-默认的拷贝构造函数(如果不定义)执行浅拷贝:即逐成员复制。如果成员是指针,则复制指针值(地址),而不是指针指向的内容。这会导致两个对象指向同一块内存,可能引发双重释放等问题。-深拷贝:在拷贝构造函数中,为指针成员分配新的内存,并复制内容。这样两个对象拥有独立的资源。例如,如果类中有指针成员:```cppclassMyArray{public:MyArray(intsize):size_(size),data_(newint[size]){}//自定义拷贝构造函数(深拷贝)MyArray(constMyArray&other):size_(other.size_),data_(newint[other.size_]){for(inti=0;i<size_;i++){data_[i]=other.data_[i];}}//...需要定义析构函数释放data_,以及赋值操作符(遵循三法则)private:intsize_;int*data_;};```最后,根据引用[2][4],在C++11以后,我们还可以定义移动构造函数(用于临时对象的资源转移),但用户当前问题不涉及,可以暂时不提或简单提及。相关问题:1.拷贝构造函数和赋值操作符有什么区别?2.什么情况下需要自定义拷贝构造函数?3.什么是深拷贝和浅拷贝?请举例说明。4.在C++中,如何防止对象被拷贝?(使用=delete)5.移动构造函数与拷贝构造函数有何不同?注意:用户要求生成相关问题,所以我们在最后添加。</think>### C++ 拷贝构造函数详解 #### 基本概念 拷贝构造函数C++中的特殊成员函数,用于**创建一个新对象作为现有对象的副本**。其标准声明形式为: ```cpp ClassName(const ClassName& other); ``` - **核心作用**:初始化新对象时复制已有对象的状态 - **调用时机**: - 对象初始化:`T b = a;` 或 `T b(a);` - 函数参数传递(按值传递对象) - 函数返回对象(某些优化场景) #### 关键示例 参考引用[1]的自定义拷贝构造函数: ```cpp #include <iostream> using namespace std; class Student { public: // 普通构造函数 Student(int id) : id_(id) {} // 自定义拷贝构造函数 Student(const Student& other) { id_ = other.id_ * (-1); // 自定义复制行为:取负值 cout << "拷贝构造函数调用!源ID: " << other.id_ << ", 新ID: " << id_ << endl; } void print() const { cout << "当前ID: " << id_ << endl; } private: int id_; }; int main() { Student alice(1001); // 普通构造 alice.print(); // 输出: 当前ID: 1001 Student bob(alice); // 拷贝构造 (直接初始化) bob.print(); // 输出: 当前ID: -1001 Student charlie = alice; // 拷贝构造 (拷贝初始化) charlie.print(); // 输出: 当前ID: -1001 return 0; } ``` **输出结果**: ``` 当前ID: 1001 拷贝构造函数调用!源ID: 1001, 新ID: -1001 当前ID: -1001 拷贝构造函数调用!源ID: 1001, 新ID: -1001 当前ID: -1001 ``` #### 核心特性 1. **与赋值的区别**(引用[1]) ```cpp Student david; david = alice; // 调用赋值运算符=,非拷贝构造 ``` - 拷贝构造函数:创建**新对象**时使用 - 赋值运算符:**已存在对象**的重新赋值 2. **深拷贝 vs 浅拷贝** - **浅拷贝**(默认):逐成员复制(指针复制地址) ```cpp // 默认拷贝构造等效于 Student(const Student& other) : id_(other.id_) {} // 指针成员会复制地址值! ``` - **深拷贝**:需手动实现,复制指针指向的内容 ```cpp class DataHolder { public: DataHolder(int size) : size_(size), data_(new int[size]) {} // 深拷贝构造函数 DataHolder(const DataHolder& other) : size_(other.size_), data_(new int[other.size_]) { copy(other.data_, other.data_ + size_, data_); } ~DataHolder() { delete[] data_; } private: int size_; int* data_; // 动态资源 }; ``` 3. **现代C++优化**(引用[2][4]) - 移动语义可避免不必要的拷贝: ```cpp vector<Student> students; students.push_back(Student(1002)); // 优先调用移动构造 students.push_back(move(alice)); // 显式移动 ``` #### 最佳实践 1. **三法则**:若定义拷贝构造/析构/赋值之一,通常需定义全部 2. **禁止拷贝**(C++11+): ```cpp Student(const Student&) = delete; ``` 3. **传递const引用**避免拷贝: ```cpp void processStudent(const Student& s); // 高效传参 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值