C++开发初级


910 浏览 5 years, 3 months

3.1.6 复制构造函数

版权声明: 转载请注明出处 http://www.codingsoho.com/
复制构造函数

C++中有一种特殊的构造函数叫做复制构造函数(copy constructor),允许所创建的对象是另一个对象的精确副本。如果您没有编写复制构造函数,C++会自动生成一个,用源对象中相应数据成员的值初始化新对象的每个数据成员。如果数据成员 是对象,初始化意味着调用它们的复制构造函数。

class SpreadsheetCell
{
 public:
  SpreadsheetCell(const SpreadsheetCell& src);
};

代码取自 SpreadsheetCellCopyCtor\SpreadsheetCell.h

复制构造函数采用源对象的const引用作为参数。与其他构造函数类似,它也没有返回值。在这个构造函数内部,您应该复制源对象的所有数据字段。

当然,从技术上讲您可以在复制构造函数内完成任何事情,但是最好按照预期的行为将新对象初始化为己有对象的副本。下面是SpreadsheetCell复制构造函数的一个示例实现:

SpreadsheetCell::SpreadsheetCell() : mValue(0), mString("")
{
}

代码取自 SpreadsheetCellCopyCtor\SpreadsheetCell.cpp

注意ctor-initializer的用法。在ctorinitializer中设置值以及在复制构造函数体内设置值的区别将在后面关于赋值的小节中介绍。

假定有一组成员变量,名称为m1, m2...mn,编译器生成的复制构造函数为:

classname::classname(const classname & src) :m1(src.m1)m2(src.m2),…mn(src.mn) {}

因此在多数情况下,不需要亲自编写复制构造函数.然而在某些情况下,默认复制构造函数功能不足。后面将讲述这些情况。

什么时候调用复制构造函数

C++中传递函数参数的默认方式是值传递,这意味着函数或者方法接收某个值或者对象的副本。因此,无论什么时候给函数或者方法传递一个对象,编译器都会调用新对象的复制构造函数进行初始化。例如,SpreadsheetCell类中setString()方法的定义如下:

void SpreadsheetCell::setString(string inString)
{
  mString = inString;
  mValue = stringToDouble(mString);
}

C++字符串实际上是一个类而不是内建类型。当调用setString()并传递一个String参数时,这个String参数inString会调用复制构造函数进行初始化。传递给复制构造函数的参数是传递给setString()的字符串。在下面的示例中,为了初始化setString()中的 inString对象,会调用string复制构造函数,其参数为name:

SpreadsheetCell myCell;
string name = "heading one"
myCell.setString(name); // Copies name

当setSting()方法结束时,inString被销毁,因为它只是name的一个副本,所以name完好无缺。

当函数或者方法返回对象的时候,也会调用复制构造函数。在此情况下,编译器使用复制构造函数创建一个临时的、没有名称的对象,后面将详细介绍临时对象的作用。可以将传递的参数作为const引用,从而避免调用复制构造函数的开销,下一节将讲述这一内容。

显式调用复制构造函数

您也可以显式地使用复制构造函数,从而将某个对象作为另一个对象的精确副本。

例如,可以这样创建spreadsheetCell对象的副本:

SpreadsheetCell  myCell2(4);
SpreadsheetCell  myCell3(myCell2); // myCell3 has the same value as myCell2

代码取自 SpreadsheetCellCopyCtor\SpreadsheetCellTest.cpp

按引用传递对象

当向函数或者方法传递对象时,为了避免复制对象,可以让函数或者方法采用对象的引用做参数。按引用传递对象通常比按值传递对象效率更高,因为只需要复制对象的地址,而不需要复制对象的全部内容。此外,按引用传递可以避免对象动态内存分配的问题,这些内容将在后面讲述。

当传递某个对象的引用时,使用对象引用的函数或者方法可以修改原始对象。如果只是为了提高效率才按引用传递,可将对象声明为const以排除这种可能。

下面是SpreadsheetCell类的定义,其中将字符串对象作为const引用传递:

class SpreadsheetCell
{
 public:
  SpreadsheetCell();
  SpreadsheetCell(double initialValue);
  SpreadsheetCell(const string& initialValue);
  SpreadsheetCell(const SpreadsheetCell& src);
  void setValue(double inValue);
  double getValue() const;
  void setString(const string& inString);
  string getString() const;

 protected:
  string doubleToString(double inValue) const;
  double stringToDouble(const string& inString) const;

  double mValue;
  string mString;
};

代码取自 SpreadsheetCellCopyCtor\SpreadsheetCell.h

下面是setString()的实现。注意方法体并没有变化,只是参数类型有所不同:

void SpreadsheetCell::setString(const string& inString)
{
  mString = inString;
  mValue = stringToDouble(mString);
}

代码取自 SpreadsheetCellCopyCtor\SpreadsheetCell.cpp

为了提高性能,最好按const引用传递而不是按值传递对象.

返回string的SpreadsheetCell方法仍然是按值返回。返回数据成员的引用存在风险,因为只有对象“存在”时引用才有效。一旦对象被销毁,引用就失效。然而有时候返回数据成员的引用是合理的,本章后面以及后续章节将介绍相关内容。

显式默认或者删除复制构造函数(仅限C++11)

可以采用下面的方法将编译器生成的复制构造函数设为默认或者将其删除:

SpreadsheetCell(const SpreadsheetCell& src) = default;

或者

SpreadsheetCell(const SpreadsheetCell& src) = delete;