2008年1月24日星期四

拷贝构造函数的相关内容(浅拷贝,深拷贝,赋值运算符)

Copy constructor is
  • a constructor function with the same name as the class
  • used to make deep copy of objects.
拷贝构造函数(或者叫复制构造函数)是与构造函数同名的函数,它来实现对象的深拷贝。
什么是深拷贝和浅拷贝?浅拷贝复制的是对象的所有成员的值,对于指针,它只复制指针,而不会复制其指向的动态分配的内存。默认的拷贝构造函数和赋值运算符实现的是浅拷贝。浅拷贝的问题是,如果存在多个指针指向同一处内存,而这段内存在某个时候由于一个对象的结束而被释放,那么其他对象的指针就指向了无效的内存区域!
深拷贝必须通过自己编写拷贝构造函数和重载赋值运算符来实现,它将动态分配的内存也实现拷贝。

There are 3 important places where a copy constructor is called.
  • When an object is created from another object of the same type
  • When an object is passed by value as a parameter to a function
  • When an object is returned from a function
拷贝构造函数主要在下述的三种情况下被调用:
  • 当从另一个同类型的对象中创建对象时

Person q("Mickey"); // constructor is used to build q. no copy constructor
Person r(p); // copy constructor is used to build r.
Person p = q; // copy constructor is used to initialize in declaration.
p = q; // Assignment operator, no constructor or copy constructor
  • 当对象作为一个函数的参数用值传递的方式传递时。

f(p); // copy constructor initializes formal value parameter.

  • 当对象被函数用作返回值返回时。
当对象间赋值的时候,不会调用拷贝构造函数,这时候使用的是赋值运算符,请查看文末的说明。
If a copy constructor is not defined in a class, the compiler itself defines one. This will ensure a shallow copy. If the class does not have pointer variables with dynamically allocated memory, then one need not worry about defining a copy constructor. It can be left to the compiler's discretion.
But if the class has pointer variables and has some dynamic memory allocations, then it is a must to have a copy constructor.
如果不在类中定义拷贝构造函数,编译器会自动定义一个,完成浅拷贝。如果类中没有指针变量指向动态分配的内存,浅拷贝就OK,那么就没有必要自定义拷贝构造函数
如果有,必须进行深拷贝的话,则必须定义拷贝构造函数

If you need a copy constructor, you also need a destructor and operator=
如果需要一个拷贝构造函数,那么你同样需要一个析构函数和对赋值运算符=的重载。

例如:
class A //Without copy constructor
{
private:
int x;
public:
A() {x = 10;}
~A() {}
}
class B //With copy constructor
{
private:
char *name;
public:
B()
{
name = new char[20];
}
~B()
{
delete name[];
}
//Copy constructor
//拷贝构造函数的参数必须为const,这样可以防止原来的对象被破坏,同时,它必须是引用,因为如果不是引用而用值传递,那么就需要拷贝,就需要调用拷贝构造函数,就需要拷贝,就需要.....
B(const B &b)
{
name = new char[20];
strcpy(name, b.name);
}
};

Let us Imagine if you don't have a copy constructor for the class B. At the first place, if an object is created from some existing object, we cannot be sure that the memory is allocated. Also, if the memory is deleted in destructor, the delete operator might be called twice for the same memory location.
设想没有为B类定义拷贝构造函数,首先,如果新的对象通过一个已经存在的对象来创建,那么我们不能肯定内存被正确地分配了。并且,内存被析构函数删除,同一处内存可能会被删除两次。
Difference between copy constructor and assignment
拷贝构造函数和赋值的区别
A copy constructor is used to initialize a newly declared variable from an existing variable. This makes a deep copy like assignment, but it is somewhat simpler:
  • There is no need to test to see if it is being initialized from itself.
  • There is no need to clean up (eg, delete) an existing value (there is none).
  • A reference to itself is not returned.
拷贝构造函数从一个已经存在的变量来初始化一个新声明的变量。这使得深拷贝和赋值有些相像。但是更简单:
首先,不需要像赋值运算那样检查是否从自己初始化
其次,不需要清除现有的值(因为是新创建,所以没有现有值)
最后,没有返回值。

赋值运算符的定义如下:
The following steps are a typical for the assignment operator.
  1. No self-assignment. Test to make sure you aren't assigning an object to itself, eg x = x. Of course no one would write that statement, but in can happen when one side of the assignment is not so obviously the same object. Self assignment fails because the memory associated with the current value of the left-hand-side is deallocated before the assignment, which would invalidate using it from the right-hand-side.
  2. Delete the associated memory. Same as copy constructor.
  3. Copy all the members. Same as copy constructor.
  4. Return *this. This is necessary to allow multiple assignment, eg x = y = z;

//--- file Person.h
. . .
class Person {
private:
char* _name;
int _id;
public:
Person& Person::operator=(const Person& p);
. . .
}
//--- file Person.cpp
. . .
//=================================================== operator=
Person& Person::operator=(const Person& p) {
if (this != &p) { // make sure not same object
delete [] _name; // Delete old name's memory.
_name = new char[strlen(p._name)+1]; // Get new space
strcpy(_name, p._name); // Copy new name
_id = p._id; // Copy id
}
return *this; // Return ref for multiple assignment
}//end operator=

如果存在基类,那么在重载赋值运算符时,也必须调用基类的赋值运算符
//--- file Parent.h
class Parent {...}; // declaration of base class
//--- file Child.h
#include "Parent.h"
class Child : public Parent { // declaration of derived class
public:
Child& Child::operator=(const Child& source);
};//end class Child
//--- file Child.cpp
#include "Child.h"
Child& Child::operator=(const Child& source) {
if (this != &source) {
this->Parent::operator=(source);
. . . // copy all our own fields here.
}
return *this;
}//end operator=
其他:
为了防止拷贝和值传递等方式对对象进行错误的赋值,可以将拷贝构造函数定义为私有,并且并不实现她,对赋值运算符也一样。

参考文献:
http://www.codersource.net/cpp_copy_constructors.html
http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html
http://www.fredosaurus.com/notes-cpp/oop-overloading/overloadassign.html
http://cse.stanford.edu/class/cs193d/handouts/14CopyConstructors.pdf

没有评论: