StupidBeauty
Read times:6562Posted at:Wed Aug 22 12:04:31 2018 - no title specified

《C++面向对象软件设计及构建》文章翻译:4.9 复制构造函数,4.9 Copy Constructors

通过一个名为复制构造函数的特殊构造函数,一个类可以定义出它的实例是该如何被复制的。复制构造函数,是利用某个已有对象的属性(数据、状态、实例变量)来对新创建的对象进行初始化的。

复制构造函数,是一个具有特殊的、固定的格式的构造函数。跟其它的构造函数一样,复制构造函数,不可以被显式地调用,而只是会在C++语言认为需要对某个对象进行复制时被隐式调用。

如果某个类中并未定义复制构造函数,那么,会使用某个默认的复制构造函数,这个默认的复制构造函数会逐个字节地将已有对象的数据复制到新对象中。在狠多简单的情景下,使用默认的复制构造函数就足够了。然而,正像如下示例所展示的那样,默认的复制构造函数经常是不合适的。另外,有狠多人认为,明确地定义一个复制构造函数是一个狠好的习惯,即使它会与默认的复制构造函数起到完全相同的作用也没关系。

在两种情况下,会隐式地使用复制构造函数。第一种是,声明了一个新的对象,并且使用某个已有的对象来对它进行初始化。以下代码展示了这种情况:

Frame  window1("First", Location(100, 100), Shape (200,300));

Frame  window2("Second", Location(100, 100), Shape (200,300));

Message originalMsg("Hello World");

Message copiedMsg( originalMsg );  // 使用 了复制构造函数来对新对象进行初始化

在这个示例中,在声明Message 类的对象copiedMsg 时,使用了为Message 类而定义的复制构造函数。对象copiedMsg 中的那些数据成员的初始值,是根据对象originalMsg 中的数据成员的值而初始化的。第二种会隐式使用复制构造函数的情况是,将某个对象以传值的方式作为参数来传递。这种情况,由下面定义的StatusLine 类来展示。每当StatusLine 的构造函数或它的SetStatus 方法被使用时,就会对Message 参数进行一次复制,因为,这两种情况下的参数都是按值传递的。


StatusLine 类的接口

  Class StatusLine {

     private:

       Message statusMessage;

     public:

       StatusLine(Message msg);

       void SetStatus(Message newMsg);

   };


在上面所描述的情况中,使用默认的复制构造函数是不合适的,它不能确保Message 类的对象被安全地复制。回忆一下,在Message 类的实现中,使用了一个字符串(一个char*)来储存某个指针,该指针即定义了要在屏幕上显示的文字的内容。为了避免内存泄漏,当Message 对象被删除时,在Message 类的析构函数中,正确地删除了这个字符串。Message 类中相应的那部分代码是:

class Message {

private:

char*    message;

...

public:

...

~Message();                            // 删除 该消息对象

};

Message::~Message(){ delete message; }

如果使用默认复制构造函数来对 Message 对象进行复制,那么,就会复制指针(message),而不是复制该指针所指向的字符串。使用默认复制构造函数,就会产生如下的结果:


共享数据

两个对象,都指向内存中同一个地方。只要两个对象都还存在,就没有问题。然而,一旦其中的一个对象被删除了,这个字符串就会被该个对象的析构函数销毁掉,这就会导致,另外一个对象指向了内存中一块内容不确定的区域。

在这种情况下,会在遇到以下情形时出错。针对以上场景,第一次调用Display方法时,会创建originalMsg对象的一个副本。在Display方法结束时,这个副本会被销毁。于是,originalMsg 对象和copiedMsg 对象会同时指向一个具有不确定内容的内存区域。在第二次调用Display 方法时,就会发生以下三种情况之一:

  1. 1.在window2 中显示了正确的字符串("Hello World");如果被删除的内存区域并未立即被回收,或者即使被回收了却也并未被立即改变内容,那么就会发生这种情况。在某段不确定长度的时间内,这块内存可能都会维持相同的内容。

  2. 2.在window2 中显示一个不正确的(垃圾)字符串;如果被删除的内存区域被回收了,并且被改变成某种看起来像字符串却又不一定是字符串的内容,就会发生这种情况。

  3. 3.程序会崩溃。如果被删除的内存区域被回收了,且其内容被改变成了某种无法被解释成字符串的内容,就会发生这种情况。

Message 类的复制构造函数,展示了,应当如何定义复制构造函数:

class Message {

private:

...

public:

Message(const Message& other);    // 复制构造函数

};

复制构造函数只有一个参数 - 指向相同的类(在这种情况下,就是Message类)的某个对象的引用,且其值不会改变(也就是说,它被声明为常量(const))。此处的输入参数(other)被声明为常量(const),因为,复制构造函数在初始化新对象的过程中,无需对参数的值进行改变。

Message 类的复制构造函数的代码如下:

Message::Message(const Message& other) {

message = copystring(other.message); }

这个构造函数中,会将 other 对象中定义的字符串复制一份。在这种情况下,每当其中一个对象被销毁时,只有它自己的那个字符串被删除。不会发生内存泄漏,也不会产生野指针。

任务

  1. 1.为Shape 类添加一个复制构造函数。使用一个简单的主程序来测试妳的代码。

  2. 2.为Location 类添加一个复制构造函数。使用一个简单的主程序来测试妳的代码。

  3. 3. PolyShape 类添加一个复制构造函数。 使用一个简单的主程序来测试妳的代码。

  4. 4. 检查 StopWatch  类的实现。 是否使用默认的复制构造函数就足够用来安全地复制StopWatch 对象,还是说,需要专门写一个复制构造函数?

未知美人

azumi harusaki

未知美人

Your opinions
Your name:Email:Website url:Opinion content:
- no title specified

HxLauncher: Launch Android applications by voice commands