C++ 默认复制构造函数备忘

来源:互联网 发布:python 创建两个字典 编辑:程序博客网 时间:2024/06/01 16:14

      在《高质量编程指南_林锐》的第九章开头部分有一段话,

 

      "缺省的拷贝构造函数 和 缺省的赋值函数 均采用 '位拷贝' 而非 '值拷贝' 的方式实现, 倘若类中含有指针变量, 这两个函数注定将出错 "

 

      其中吸引我眼球的是 "位拷贝" 这个字眼, E文好像是 "bitwise copy", 全称"逐位拷贝". 没看过E文原版的, 这些是网上看到的.

 

      说实话, 抛出一个概念性的东西, 而没有作一定的解释, 是很不负责任的行为.

 

      网上的有些人说 '位拷贝' 就是 '浅拷贝', 拷的是地址; '值拷贝' 应该就算是 '深拷贝' 吧, 拷的是内容.

 

-------------------------------------------------可爱的分割线 -------------------------------------------------------------

 

      其实以上的都是废话, 在 <C++ Primer 第四版> 中第13章<复制控制>的开头部分最后一句话:

 

      有一种特殊常见的情况需要类定义自己的复制控制成员的: 类具有指针成员. (复制构造函数, 赋值操作符 和 析构函数总称为复制控制copy control)

 

      在 13.1.1 合成的复制构造函数(synthesized copy constructor) 中, 合成的复制构造函数就是缺省的拷贝构造函数, 他的行为是, 执行逐个成员初始化(menberwise initialize), 将新对象初始化为原对象的副本.  

 

      所谓"逐个成员初始化", 指的是编译器将现有对象的每个非 static 成员, 依次复制到正创建的对象. 只有一个例外, 每个成员的类型决定了复制该成员的含义. 合成复制构造函数直接复制内置类型成员的值, 类类型成员使用该类的复制构造函数进行复制. 数组成员的复制是个例外, 虽然一般不能复制数组, 但如果一个类具有数组成员, 则合成复制构造函数将复制数组. 复制数组时合成复制构造函数将复制数组的每一个元素.

 

      如 class ClassA

          {

                .........

                private:

                     int ia;
                     int *pb;

          }

 

       那么, 合成复制改造函数如下所示

       ClassA::ClassA(const ClassA &orig):

                         ia(orig.ia), pb(orig.pb)

       {}

 

       //写一个ClassB, 有自己的复制构造函数, 一个ClassC, 它的一个成员变量是ClassB 对象, ClassC c; ClassC d(c); 事实证明一切

 

       看到 pb(orig.pb) 了没, 指针pb 是指针orig.pb 的一个副本, 但是他们的指向是相同的.  *pb 和 *orig.pb 是指向同一个资源, 他们之间任何一个修改都会互相影响. 因为指针会出现这种情况, 所以<c++ primer 第四版>13章开头才写了那么一句话.

 

       而那些什么 '位拷贝' 和 '值拷贝' 更象是一个混饶我们思维的一个东西.

 

       如果还是要分'位拷贝' 或 '值拷贝', 个人看法, 这个成员变量都是 '值拷贝', 不管是内置类型, 类类型 或 指针, 它们的地址都跟被复制的对象不同, 证明他们都被分配了内存空间. 指针成员变量指向什么地址是另一回事了.

 

       测试例子很简单, 即不贴了.

 

 

 

 

 

 

 -------------------------------------------------可爱的分割线 -------------------------------------------------------------

<C++ Primer 第四版> 15.4.3节

 

 

1. 如果派生类定义了自己的复制构造函数, 该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:

 

       class Base{/*..............*/};

 

       class Derived: public Base{

           public:

              //Base::Base(const Base&) not invoked automatically

              Derived(const Derived &d):

                       Base(d)      /* other member initialization */

              {/*..............................*/}   

        };

 

     初始化函数Base(d) 将派生类对象d 转换为它的基类部分的引用, 并调用基类复制构造函数. 如果省略基类初始化函数, 如下代码:

 

              Derived(const Derived &d)

              {/*..............................*/}   

 

      效果是运行Base 的 默认构造函数 初始化对象的基类部分. 假定 Derived 成员的初始化从 d 复制对应成员, 则新构造的对象将具有奇怪的配置: 它的 Base 部分将保存默认值, 而它的Derived 成员是另一对象的副本.

 

 

 

2. 赋值操作符通常与复制构造函数类似: 如果派生类定义了自己的赋值操作符, 则该操作符必须对基类部分进行显式赋值:

 

         //Base::operator=(const Base&) not invoked automatically

         Derived &Derived::operator=(const Derived &rhs)

        {

              if(this != rhs){

                    Base::operator=(rhs);         //assigns the base part

                    ................................

              }

 

               return *this;

        }

     

      赋值操作符必须防止自身赋值. 假定左右操作数不同, 则调用 Base 类的赋值操作符给基类部分赋值. 该操作符可以由类定义, 也可以是合成赋值操作符, 这没什么关系-----------我们可以直接调用它. 基类操作符将释放左操作数中基类部分的值, 并赋以来自rhs 的新值. 该操作符执行完毕后, 接着要做的是为派生类中的成员赋值.

 

 

 

3. 析构函数的工作与复制构造函数和赋值操作符不同: 派生类析构函数不负责撤销基类对象的成员. 编译器总是显式调用派生类对象基类部分的析构函数. 每个析构函数只负责清除自己的成员:

 

           //Base::~Base()  invoked automatically

           Derived::~Derived () {/* do what it takes to clean up derived members */}

 

        对象的撤销顺序与构造函数相反: 首先运行派生类析构函数, 然后按继承层次依次向上调用个基类析构函数.

 

 

 

 

 

 

原创粉丝点击