大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++箴言:拷贝一个对象的所有组成部分

C++箴言:拷贝一个对象的所有组成部分

关键词:C++拷贝对象  阅读(535) 赞(14)

[摘要]本文是对C++箴言:拷贝一个对象的所有组成部分的讲解,对学习C++编程技术有所帮助,与大家分享。

  在设计良好的面向对象零碎中,为了紧缩其对象外部的空间,仅留两个函数用于对象的拷贝:普通称为拷贝结构函数(copy constructor)和拷贝赋值运算符(copy assignment operator)。我们将它们统称为拷贝函数(copying functions)。假定需求,编译器会生成拷贝函数,而且说明了编译器生成的版本正象你所希冀的:它们拷贝被拷贝对象的全部数据。

  当你声明了你本人的拷贝函数,你就是在通知编译器你不喜欢缺省完成中的某些东西。编译器对此似乎勃然大怒,而且它们会用一种乖僻的方式报复:当你的完成存在一些简直可以确定错误时,它偏偏不通知你。

  思索一个意味消费者(customers)的类,这里的拷贝函数是手写的,以便将对它们的调用记入日志:

void logCall(const std::string& funcName); // make a log entry

class Customer {
 public:
  ...
  Customer(const Customer& rhs);
  Customer& operator=(const Customer& rhs);
  ...

 private:
  std::string name;
};
Customer::Customer(const Customer& rhs)
: name(rhs.name) // copy rhs’s data
{
 logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs)
{
 logCall("Customer copy assignment operator");
 name = rhs.name; // copy rhs’s data
 return *this; // see Item 10
}

  这里的每一件事看起来都不错,实践上也的确不错——直到 Customer 中参与了另外的数据成员:

class Date { ... }; // for dates in time

class Customer {
public:
 ... // as before

private:
 std::string name;
 Date lastTransaction;
};

  在这里,已有的拷贝函数只中止了局部拷贝:它们拷贝了 Customer 的 name,但没有拷贝它的 lastTransaction。但是,大局部编译器对此毫不在意,即便是在最高的正告级别(maximal warning level)。这是它们在对你写本人的拷贝函数中止报复。你回绝了它们写的拷贝函数,所以假定你的代码是不完善的,他们也不通知你。结论不言而喻:假定你为一个类添加了一个数据成员,你务必要做到更新拷贝函数。(你还需求更新类中的全部的结构函数以及任何非规范方式的 operator=。这个效果最为迷惑人的情形之一是它会经过承袭发作。思索:

class PriorityCustomer: public Customer { // a derived class
 public:
  ...
  PriorityCustomer(const PriorityCustomer& rhs);
  PriorityCustomer& operator=(const PriorityCustomer& rhs);
  ...

 private:
  int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority)
{
 logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
 logCall("PriorityCustomer copy assignment operator");
 priority = rhs.priority;
 return *this;
}
 

  PriorityCustomer 的拷贝函数看上去似乎拷贝了 PriorityCustomer 中的每一样东西,但是再看一下。是的,它的确拷贝了 PriorityCustomer 声明的数据成员,但是每个 PriorityCustomer 还包括一份它从 Customer 承袭来的数据成员的正本,而那些数据成员基本没有被拷贝!PriorityCustomer 的拷贝结构函数没有指定传递给它的基类结构函数的参数(也就是说,在它的成员初始化列表中没有提及 Customer),所以,PriorityCustomer 对象的 Customer 局部被 Customer 的结构函数在无参数的状况下初始化——运用缺省结构函数。(假定它有,假定没有,代码将无法编译。)那个结构函数为 name 和 lastTransaction 中止一次缺省的初始化。

  关于 PriorityCustomer 的拷贝赋值运算符,状况有些微的不同。它不会试图用任何办法改动它的基类的数据成员,所以它们将坚持不变。

  无论何时,你方案本人为一个派生类写拷贝函数时,你必需留意同时拷贝基类局部。那些中央的典型特征当然是 private,所以你不能直接拜访它们。派生类的拷贝函数必需调用和它们对应的基类函数:

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs), // invoke base class copy ctor
priority(rhs.priority)
{
 logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
 logCall("PriorityCustomer copy assignment operator");

 Customer::operator=(rhs); // assign base class parts
 priority = rhs.priority;
 return *this;
}

  本文中的 "copy all parts" 的含义如今应该清楚了。当你写一个拷贝函数,需求保证拷贝一切本地数据成员以及调用一切基类中的适当的拷贝函数。

  在实践中,两个拷贝函数常常有类似的函数体,而这一点可以吸引你试图经过用一个函数调用另一个来防止代码反复。你希望防止代码反复的想法值得一定,但是用一个拷贝函数调用另一个来做到这一点是错误的。

  用拷贝赋值运算符调用拷贝结构函数是没有意义的,由于你这样做就是试图去结构一个曾经存在的对象。这太荒唐了,甚至没有一种语法来支持它。有一种语法看起来似乎能让你这样做,但实践上你做不到,还有一种语法采用迂回的办法这样做,但它们在某种条件下会对毁坏你的对象。所以我不方案给你看任何那样的语法。无条件地承受这个观念:不要用拷贝赋值运算符调用拷贝结构函数。 尝试一下另一种相反的办法——用拷贝结构函数调用拷贝赋值运算符——这异常是荒唐的。一个结构函数初始化新的对象,而一个赋值运算符只能用于曾经初始化过的对象。借助结构进程给一个对象赋值将意味着对一个尚未初始化的对象做一些事,而这些事只需用于已初始化对象才有意义。几乎是胡搞!不要做这种尝试。

  作为一种替代,假定你发现你的拷贝结构函数和拷贝赋值运算符有类似的代码,经过创立第三个供两者调用的成员函数来消弭反复。这样的函数当然是 private 的,而且常常叫做 init。这一战略是在拷贝结构函数和拷贝赋值运算符中消弭代码反复的平安的,被证明过的办法。

  Things to Remember

  ·拷贝函数应该保证拷贝一个对象的一切数据成员以及一切的基类局部。

  ·不要试图根据一个拷贝函数完成另一个。作为替代,将通用功用放入第三个供单方调用的函数。



相关评论