C++对象模型——Copy Constructor 的建构操作(第二章)
来源:互联网 发布:心算除法的快速计算法 编辑:程序博客网 时间:2024/05/29 04:18
2.2 Copy Constructor 的建构操作
有三种情况,会以一个object的内容作为另一个 class object的初值,最明显的一种情况就是对一个object做显式的初始化操作,例如:class X { ... };X x;// 明确地以一个object的内容作为另一个class object的初值X xx = x;另两种情况是当object被当作参数交给某个函数时,例如
extern void foo(X x);void bar() { X xx; // 以xx作为foo()第一个参数的初值(隐式的初始化操作) foo(xx);}以及当函数传回一个 class object时。例如:
X foo_bar() { X xx; return xx;}假设 class 设计者明确定义了一个copy constructor(这是一个constructor,有一个参数的类型是其 class type),例如:
// user-defined copy constructor的实例// 可以是多参数形式,其第二个参数及后继参数以一个默认值供应之X::X(const X &x);Y::Y(const Y &y, int = 0);那么在大部分情况下,当一个 class object以另一个同类实体作为初值时,上述的constructor会被调用。这可能会导致一个暂时性 class object的产生或程序代码的蜕变。
Default Memberwise Initialization
如果 class 没有提供一个explicit copy constructor会怎样?当 class object以"相同class的另一个object"作为初值时,其内部是以所谓的default memberwise initialization完成的,也就是把每一个内建的或派生的data member(例如一个指针或数组)的值,从某个object拷贝一份到另一个object,不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。例如,考虑下面这个 class 声明:class String {public: // ... 没有 explicit copy constructorprivate: char *str; int len;};一个String object的default memberwise initialization发生在这种情况下:
String noun("book");String verb = noun; 其完成方式就像个别设定每一个members一样:verb.str = noun.str;verb.len = noun.len;如果一个String object被声明为另一个 class 的member,如下所示:
class Word {public: // ... 没有 explicit copy constructorprivate: int _occurs; String _word; // String object成为class Word的一个member};那么一个Word object的default memberwise initialization会拷贝其内建的member _occurs,然后再从String member object _word递归实施memberwise initialization。
这样的操作如何实际上是怎样完成的?ARM指出:
从概念上而言,对于一个 class X,这个操作是被一个copy constructor实现出来。
关键的是"概念上",这个注释紧跟着一些解释:
一个良好的编译器可以为大部分 class objects产生bitwise copies,因为它们有bitwise copy semantics...
也就是说,"如果一个class未定义出copy constructor,编译器就自动为它产生出一个"这句话是不对的,而是应该像ARM所说:
Default constructors和copy constructors在必要的时候采油编译器产生出来。
这个句子的"必要"是指 class 不展现bitwise copy semantics时。C++ Standard仍然保留了ARM的意义,但将相关讨论更形式化如下:
一个 class object可以从两种方式复制得到,一种是被初始化,另一种是被指定(assignment)。从概念上而言,这两个操作分别是以copy constructor和copy assignment operator 完成的。
就像default constructor一样,C++ Standard指出,如果 class 没有声明一个copy constructor,就会有隐式的声明出现。C++ Standard把copy constructor区分为trivial和nontrivial两种,只有nontrivial的实体才会被合成于程序中,决定一个copy constructor是否为trivial的标准在于 class 是否展现出所谓的"bitwise copy semantics"。
Bitwise Copy Semantics (位逐次拷贝)
在下面的程序片段中:#include "Word.h"Word noun("book");void foo() { Word verb = noun;}很明显verb是根据noun来初始化,但在尚未看到 class Word声明之前,不可能预测这个初始化操作的程序行为,如果 class Word的设计者定义了一个copy constructor,verb的初始化操作会调用它,但如果该 class 没有定义explicit copy constructor,那么是否会有一个编译器合成的实体被调用呢?这就视该 class 是否展现"bitwise copy semantics"而定。如下所示:
// 以下声明展现了bit copy semanticsclass Word {public: Word(const char *); ~Word() { delete []str; }private: int cnt; char *str;};这种情况下并不需要合成出一个default copy constructor,因为上述声明展现了"default copy semantics",因此verb的初始化操作就不需要一个函数调用,然而,如果 class Word是这样声明的:
// 以下声明并未展现出bitwise copy semanticsclass Word {public: Word( const String &); ~Word();private: int cnt; String str;}; 其中String声明了一个explicit copy constructor:class String {public: String(const char *); String(const String &); ~String();};在这种情况下,编译器必须合成出一个copy constructor以便调用member class String object的copy constructor:
// 一个被合成出来的copy constructor// C++伪代码inline Word::Word(const Word &wd) { str.String::String(wd.str); cnt = wd.cnt;}有一点值得注意:在这被合成出来的copy constructor中,如整数、指针、数组等等的nonclass members也都会被复制。
不要Bitwise copy Semantics
什么时候一个 class 不展现出"bitwise copy semantics"呢?有四种情况:1. 当 class 内含一个member object而后者的 class 声明有一个copy constructor时(不论是被 class 设计者显式声明,或是被编译器合成)
2. 当 class 继承自一个base class 而后者存在有一个copy constructor时
3. 当 class 声明了一个或者多个 virtual functions时
4. 当 class 派生自一个继承串链,其中有一个或者多个 virtual base classe
前两种情况中,编译器必须将members或base class 的"copy constructors调用操作"插入到被合成的copy constructor中。后两种情况有点复杂,如接下来的小节所述。
重新设定 Virtual Table的指针
编译期间的两个程序扩张操作(只要有一个 class 声明了一个或多个 virtual functions就会如此):增加一个 virtual function table(vtbl),内含每一个有作用的 virtual function的地址
将一个指向 virtual funtcion table的指针(vptr),插入到每一个 class object中
很显然,如果编译器对于每一个新产生的 class object的vptr不能成功而正确地设好其初值,将导致可怕的后果。因此,当编译器导入一个vptr到 class 中时,该 class 就不再展现bitwise semantics。现在,编译器需要合成出一个copy constructor,以求vptr适当初始化,如下所示:
首先,定义两个classes,ZooAnimal和Bear
class ZooAnimal {public: ZooAnimal(); virtual ~ZooAnimal(); virtual void animate(); virtual void draw();private: // ZooAnimal的animate()和draw() // 所需要的数据};class Bear : public ZooAnimal {public: Bear(); void animate(); void draw(); virtual void dance();private: // Bear的animate()和draw()和dance() // 所需要的数据};ZooAnimal class object以另一个ZooAnimal class object作为初值,或Bear class object以另一个Bear class object作为初值,都可以直接靠"bitwise copy semantics"完成。例如:
Bear yogi;Bear winnie = yogi;yogi会被 default Bear constructor初始化,而在constructor中,yogi的vtpr被设定指向Bear class 的 virtual table。因此,把yogi的vptr值拷贝给winnie的vptr是完全的。
当一个base class object以其derived class 内容做初始化操作时,其vptr复制操作也必须保证安全,例如:
ZooAnimal franny = yogi; // 这会发生切割(sliced)franny的vptr不可以被设定指向Bear class 的virtual table,否则当下面程序片段中的draw()被调用而franny被传进去时,就会"炸毁"(blow up)
void draw (const ZooAnimal &zoey) { zoey.draw();}void foo() { // franny的vptr指向ZooAnimal的virtual table // 而非Bear的virtual table ZooAniaml franny = yogi; draw(yogi); //调用Bear::draw() draw(franny); //调用ZooAnimal::draw()}通过franny调用virtual function draw(),调用的是ZooAnimal实体而非Bear实体(虽然franny是以Bear object yogi作为初始值)。因为franny是一个ZooAnimal object。事实上,yogi中的Bear部分已经在franny初始化时被切割(sliced)。如果franny被声明为一个reference(或者如果它是一个指针,而其值为yogi的地址),那么经由franny所调用的draw()才会是Bear的函数实体。
合成出来的ZooAnimal copy constructor会显式设定object的vptr指向ZooAnimal class 的 virtual table,而不是直接从 class object中将其vptr的值拷贝过来。
处理Virtual Base Class Subobject
Virtual base class 的存在需要特别处理,一个 class object如果以另一个object作为初值,而后者有一个 virtual base class subobject,那么也会使"bitwise copy semantics"失效。每一个编辑器对于虚拟继承的支持承诺,都表示必须让"derived class object中的virtual base class subobject位置"在执行期就准备妥当。维护"位置的完整性"是编辑器的责任。"Bitwise copy semantics"可能会破坏这个位置,所以编辑器必须在它自己合成出来的 copy constructor中做出仲裁。例如,在下面的声明中,ZooAnimal成为Raccon的一个virtual base class :
class Raccon : public virtual ZooAnimal {public: Raccon(){ /* 设定private data初值 */ } Racccon(int val) { /* 设定private data初值 */ } // ...private: // 所需要的数据};编译器所产生的代码(用以调用ZooAnimal的default constructor,将Racccon的vptr初始化,并定位出Raccon中的ZooAnimal subject)被插入在两个Raccon constructors之间。
那么"memberwise初始化"呢?一个 virtual base class 的存在会使bitwise copy semantics无效。其次,问题并不发生于"一个class object以另一个同类object作为初值",而是发生于"一个class object以其derived classes的某个object作为初值".例如让Racccon object以一个RedPanda object作为初值,而RedPanda声明如下:
class RedPanda : public Raccon {public: RedPanda() { /* 设定private data初值 */ } RedPanda(int val) { /*设定private data初值 */ }private: // ...};如果以一个Reccon object作为另一个Raccon object的初值,那么"bitwise copy"就戳戳有余了
// 简单的bitwise copy就足够Raccon rocky;Raccon little_critter = rocky;然而如果企图以一个RedPanda object作为little_critter的初值,编译器必须判断"后续当程序员企图存取其ZooAnimal subobject时是否能够正确地执行"
// 简单的bitwise copy还不够// 编译器必须明确地将litte_critter的virtual base class pointer/offset初始化RedPanda little_red;Raccon little_critter = little_red;在这种情况下,为了完成正确的little_critter初值设定,编译器必须合成一个copy constructor,插入一些码以设定 virtual base class pointer/offset的初值,对每一个members执行必要的memberwise初值化操作,以及执行其它的内存相关操作(3.4对于 virtual base classes有更详细的讨论)
在下面的情况中,编译器无法知道是否"bitwise copy semantics"还保持着,因为它无法知道Raccon指针是否指向一个真正的Raccon object,还是指向一个derived class object:
// 简单的bitwise copy可能够用,可能不够用Raccon *ptr;Raccon little_critter = *ptr;当一个初始化操作存在并保持着"bitwise copy semantics"的状态时,如果编译器能够保证object有正确而相等的初始化操作,是否它应该抑制copy constructor的调用,以使其所产生的程序代码优化?
至少在合成的copy constructor之下,程序副作用的可能性是零,所以优化似乎是合理的。如果copy constructor是由 class 设计者所提供的呢?这是一个颇有争议的问题。
上面介绍的四种情况下 class 不再保持"bitwise copy semantics",而且 default copy constructor如果未被声明的话,会被视为nontrivial,在这四种情况下,如果缺乏一个已声明的copy constructor,编译器为了正确处理"一个class object作为另一个class object的初值",必须合成出一个copy constructor。下一节介绍编译器调用ocpy constructor的策略,以及这些策略如何影响程序。
0 0
- C++对象模型——Copy Constructor 的建构操作(第二章)
- C++对象模型——Default Constructor的建构操作(第二章)
- C++对象模型之 Copy Constructor的建构操作
- 深入探索C++对象模型笔记之四 —— 构造函数语意学 (Copy Constructor的建构操作)
- Copy Constructor 的建构操作
- Copy Constructor 建构操作
- C++对象模型之Default Constructor的建构操作
- 深入探索C++对象模型笔记之三 —— 构造函数语意学 (Default Constructor的建构操作)
- Default Constructor的建构操作
- Default Constructor 的建构操作
- 读书笔记——default constructor的建构
- Default Constructor 建构操作
- 深度搜索C++对象模型2.2 构造函数语义学-Copy Constructor的构造操作
- 《深度探索C++对象模型》:copy constructor
- C++类对象的复制-拷贝构造函数——The c + + class object replication - copy constructor
- 第二章---Default constructor 的构建操作
- Inside the C++ Model第二讲之 Copy Constructor的构造操作
- 2.2copy constructor的构建操作
- bzoj1004【hnoi2008】Cards
- MyEclipse8.6注册机,仅供于个人学习使用
- 如何使用DialogFragment类来创建和显示对话框
- ubuntu安装和使用supervisor
- SSH学习六 Hibernate 一对多 多对一的双向映射
- C++对象模型——Copy Constructor 的建构操作(第二章)
- Algorithms—205.Isomorphic Strings
- myeclipse快捷键使用
- Android开发基础之AlertDialog的单选对话框的使用
- hdoj 1950(求最长顺序子序列)二分法的深度应用
- 自定义ViewGroup控件(四)----->流式布局进阶(四)
- C/C++ 常见面试题目 (一)
- TMD320DM6467T硬件调试-20150730
- SDWebImage使用