C++ 对象构造, 拷贝, 赋值和隐式类型转换总结

原文:C++ 对象构造, 拷贝, 赋值和隐式类型转换总结
作者:Breaker <breaker.zy_AT_gmail>


C++ 中对象创建(构造)、拷贝、赋值、隐式类型转换的阶段性总结

关心效率和拷贝开销,写在代码注释中

关键字:对象创建、拷贝构造、赋值、隐式类型转换、explicit、按值传递 (passed by value)、按引用传递 (passed by reference)、参数传递、返回值传递

测试环境 VC 2010

目录


对象创建总结^

右边是左边的等价语义

  • 构造函数:ctor
  • 拷贝构造函数:copy ctor,是 ctor 的一种
  • 析构函数:dtor
  • 赋值操作函数:assign, operator=
  • 初始化对象:调用某个 ctor 创建对象
  • 初始化引用:给右值起一个别名
  • 初始化语义:初始化对象 或 引用,函数参数传递、返回值传递、捕获异常对象
  • 调用 copy ctor:初始化对象,按值传递 (passed by value) 时的 函数参数传递、返回值传递、捕获异常对象
  • 默认初始化:调用默认 ctor
  • 默认 ctor:可传 0 个参数的 ctor
  • 类型转换 ctor:可传 1 个参数的 ctor
  • 内部默认 ctor:C++ 内部产生的默认 ctor
  • 内部默认 copy ctor:C++ 内部产生的 copy ctor
  • 内部默认 assign:C++ 内部产生的 assign
  • 拷贝:调用 copy ctor 或 assign
  • 按成员拷贝:内部默认 copy ctor 和 assign 的行为
  • 拷贝的传递深度:拷贝在类的继承关系(基类、基类的基类)和成员关系(成员、成员的成员)间传递的深度

拷贝的传递^

C++ 内部默认的 copy ctor 和 assign 将 copy 传递到基类和成员,顺序如下:

  1. copy 先传递到基类,基类再传递到基类的基类,按继承格方向传递
  2. 等所有基类完成 copy 后,将 copy 传递到成员,然后成员类再按照 1、2 的顺序进行 copy 传递

总之,先做基类,后做成员的 copy 传递

传递到基类的拷贝^

设 Base 是 Derive 的基类,right 是 Derive 拷贝操作时的右值

内部默认 copy ctor 传递到基类 copy ctor 的语句:

Base(right): 因为基类 copy ctor 的参数是基类引用类型,所以 可接受子类对象 right

内部默认 assign 传递到基类 assign 的语句:

Base::operator=(right): 因为基类 assign 的参数是基类引用类型,所以可接受子类对象 right
(Base&) (*this) = right: 和上面等价
(*this) = (Base&) right: 语法错误

内部类型的默认初始化^

  • 静态存储(全局和名字空间的变量、类静态成员、局部静态变量),默认初始化为适当类型的 0

  • 局部存储

    以 double 为例:

    double v1; // 未初始化,栈残留值,编译警告 double v2(); // 语义错,这是函数声明 double v3(0); // 显式初始化为 0 double v4 = double; // 语法错,编译错误 double v5 = double(); // 默认初始化为 0 double v6 = double(0); // 显式初始化为 0
  • 动态存储

    double* v1 = new double; // 未初始化,堆残留值 double* v2 = new double(); // 默认初始化为 0 double* v3 = new double(0); // 显式初始化为 0

    内部类型 v1 和 v2 有差别,不同于 class/struct

内部类型初始化示例:

// ctor Complex(const Type& real = Type(), const Type& image = Type()) : m_real(real), m_image(image) {} void user() { Complex<double> v1; Complex<double>* v2 = new Complex<double>; cout << v1 << _T("\n") << *v2 << _T("\n"); }

输出:

(0, 0) (0, 0)

构造函数重载歧义^

程序编写缺陷,编译警告

// ctor 1 Complex(const Type& real = Type(), const Type& image = Type()) : m_real(real), m_image(image) {} // ctor 2 Complex(const Type& real = Type()) : m_real(real), m_image(Type()) {} // ctor 3 Complex() : m_real(Type()), m_image(Type()) {} void user() { Complex<double> v1; // 编译错误,调用 ctor 1, 2, 3 中哪一个 Complex<double> v2(0); // 编译错误,调用 ctor 1, 2 中哪一个 Complex<double> v3(0, 0); // 无歧义,调用 ctor 1 }

创建对象^

Complex<double> v1(1.2, 2.3); // 调用 ctor Complex<double> v1 = Complex<double>(1.2, 2.3); // 和上面等价,没有 copy Complex<double> v1(Complex<double>(1.2, 2.3)); // 和上面等价,没有 copy

默认构造函数^

需要默认构造函数的地方:

  • 创建对象时,不写 () 和或 () 为空的初始化
  • 不在构造函数初始化列表中初始化的成员变量,用该成员的默认构造函数初始化
  • 不在构造函数初始化列表中初始化的基类,用基类的默认构造函数初始化

示例,考虑是否需要 UserEntry 的默认构造函数:

vector<UserEntry> user_vec; // 不需要,1 个空的 vector vector<UserEntry> user_vec[100]; // 不需要,100 个 vector,每个 vector 都为空 vector<UserEntry> user_vec(100); // 需要,1 个元素个数为 100 的 vector,每个元素用 UserEntry 默认构造函数初始化

类型转换构造函数^

外向内隐式类型转换

初始化^
Complex<double> v1 = 1.1; // 隐式调用 ctor: Complex(1.1),没有 copy
参数传递^
void func(Complex<double> v); // 前提:1. 参数为对象类型 void func(const Complex<double>& v); // 2. 参数为 const 引用类型,但不能是非 const 引用类型 // 3. 没有更匹配的重载,如 func(double v) func(1.1); // 隐式调用 ctor: Complex(1.1),没有调用 copy ctor
返回值传递^
Complex<double> func() // 前提:返回类型为对象类型,而非引用类型 { return 1.1; // 隐式调用 ctor: Complex(1.1) 创建临时对象 [temp],没有调用 copy ctor } Complex<double> v1 = func(); // 临时对象 [temp] 被约束到变量名 v1,没有额外创建新对象 Complex<double>& func() // 不能返回非 const 引用类型 { return 1.1; // 编译错误 C2440: 无法将类型 double 转换为 Complex<double>& } const Complex<double>& func() // 可以返回 const 引用类型,但不要返回局部变量 { return 1.1; // 编译警告 C4172: 返回一个局部或临时变量的地址 }
多层隐式类型转换^

不要用多层隐式类型转换

// 提供 double => Double 的转换 class Double { public: Double(double v = 0) : m_v(v) {} double m_v; }; // 提供 Double => DoubleDouble 的转换 class DoubleDouble { public: DoubleDouble(const Double& v = Double()) : m_v(v) {} Double m_v; }; // 提供 DoubleDouble => DoubleDoubleDouble 的转换 class DoubleDoubleDouble { public: DoubleDoubleDouble(const DoubleDouble& v = DoubleDouble()) : m_v(v) {} DoubleDouble m_v; };

《C++ 程序设计语言》中说不能进行多层隐式类型转换,但 VC 2010 测试结果如下:

变量初始化可进行最多 2 层隐式类型转换:

DoubleDouble v1 = 1.1; // double => Double(1.1) => DoubleDouble(Double(1.1)) DoubleDoubleDouble v2 = 1.1; // 试图 3 层隐式类型转换,编译错误 C2440: 无法将类型 double 转换为 DoubleDoubleDouble DoubleDoubleDouble v3 = Double(1.1); // Double(1.1) => DoubleDouble(Double(1.1)) => DoubleDoubleDouble(DoubleDouble(Double(1.1)))

禁止函数参数传递进行多层次隐式类型转换,2 层也不行:

void func(DoubleDouble v); func(1.1); // 编译错误 C2664: 函数无法将参数 double 转换为 DoubleDouble

返回值按值传递可进行最多 2 层隐式类型转换:

DoubleDouble func() { return 1.1; // double => Double(1.1) => DoubleDouble(Double(1.1)) } DoubleDoubleDouble func() { return 1.1; // 试图 3 层隐式类型转换,编译错误 C2664 }

explicit 类型转换构造函数^

禁止外向内隐式类型转换

explicit Complex(const Type& real = Type(), const Type& image = Type()) : m_real(real), m_image(image) {}

产生编译错误的情况:

  • 初始化隐式转换

    Complex<double> v1 = 1.1; // 编译错误 C2440
  • 参数按值传递隐式转换

    void func(Complex<double> v); func(1.1); // 编译错误 C2664
  • 返回值按值传递隐式转换

    Complex<double> func() { return 1.1; // 编译错误 C2664 }

没有编译错误的情况:

显式调用类型转换 ctor

Complex<double> v1 = Complex<double>(1.1); // 没有 copy func(Complex<double>(1.1)); Complex<double> func() { return Complex<double>(1.1); }

类型转换运算符^

参考《C++ 程序设计语言》11.4 转换运算符

  • 类型转换构造函数 ThisType(OtherType v),外向内转换:将其它类型转换为本类型,配合本类型的 operator@

  • 类型转换运算符 ThisType::operator OtherType(),内向外转换:将本类型转换为其它类型,配合其它类型的 operator@,其它类型可以是内部类型 int 及内部操作

这两种方法都能匹配 ThisType @ ThisType

应只使用一种,不要两种都用,会产生歧义:编译器无法判断使用本类型的 operator@,还是其它类型的 operator@

类型转换运算符示例:

class Tiny { public: // 提供 int => Tiny 的转换 Tiny(int v) { assgin(v); } // 支持从 int 的 assgin Tiny& operator=(int v) { assgin(v); return *this; } // 提供 Tiny => int 的转换 operator int() const { return to_int(); } // 先写 to_int(),如果它使用的很频繁,再写 operator int() 转接到它 int to_int() const { return m_data; } private: void assgin(int v) { if (v &~ 077) throw std::out_of_range("Tiny range must be 00 ~ 077"); m_data = v; } private: char m_data; }; // CAUTION: // 不要同时使用两种类型转换和运算符重载方法 // VC 编译错误 C2666:有 2 个重载函数提供相似的类型转换,都能匹配 Tiny + Tiny: // 1. int operator +(const Tiny &, const Tiny &) // 2. C++ 内部的 operator+(int, int):因为有 Tiny => int 的转换运算符 #if 0 inline int operator+(const Tiny& left, const Tiny& right) { return left.to_int() + right.to_int(); } #endif user() { Tiny v1 = 2; Tiny v2 = 63; // Tiny v3 = 64; // 超出范围,抛出异常 int i1 = v2 + 1; // 创建 int i1 时不检查范围 // 下面是准确匹配,不会产生 Tiny(1) + Tiny(2),而是普通的内部 int + 操作 // 重载优先级:准确匹配 > 标准转换 > 用户类型转换 // 标准转换,如从 int => double 转换 int i2 = 1 + 2; Tiny v4 = v2 + 1; // 创建 Tiny v4 时,调用其 ctor 检查范围,抛出异常 v1 = v2 + 1; // Tiny.assign(int(v2 + 1)) 检查范围,抛出异常 }

拷贝构造函数^

拷贝构造函数定义^
Complex(const Complex right) // 错误的定义,引发无穷递归,因为 right 参数传递也是初始化语义,会调用 copy ctor 即自身 Complex(const Complex& right) // 普通 copy ctor,不能改变右值 Complex(Complex& right) // 用于破坏性 copy 的 copy ctor,可以改变右值
拷贝初始化^
Complex<double> v1(1.2, 2.3); // 调用 ctor Complex<double> v3(v1); // 显式调用 copy ctor: v2 <= v1,仅一次 copy,没有 assign Complex<double> v2 = v1; // 隐式调用 copy ctor,和上面等价
参数传递^
void func(Complex<double> v); // 函数结束时销毁 v func(v1); // 调用 copy ctor: v <= v1
按值传递返回值^
  • 返回局部对象

    Complex<double> func() { Complex<double> v1(2.3, 1.2); // 创建对象 v1 return v1; // 调用 copy ctor 创建临时对象:[temp] <= v1 } // 函数结束时销毁 v1

    返回时构造局部对象,不调用 copy ctor 创建第 2 个临时对象,没有销毁局部变量的开销,效率较高

    Complex<double> func_return_trans() { return Complex<double>(2.3, 1.2); // return 语句中的 Complex<double>(2.3, 1.2) 即是临时对象 [temp] }
  • 返回对象参数

    Complex<double> func(Complex<double> v) // 参数传递调用 copy ctor 创建对象 v { return v; // 调用 copy ctor 创建临时对象:[temp] <= v } // 函数结束时销毁 v
  • 返回引用参数

    Complex<double> func(Complex<double>& v) // 参数传递没有创建对象 { return v; // 调用 copy ctor 创建临时对象:[temp] <= v }
  • 返回局部静态对象

    Complex<double> func() { static Complex<double> s_v; // 第一次调用 func() 时,创建对象 s_v return s_v; // 调用 copy ctor 创建临时对象:[temp] <= s_v } // 函数结束时不销毁 s_v,程序结束时销毁 s_v
  • 返回全局对象

    包括:

    • 名字空间中的对象
    • 使用 static 内部链接方式的全局和名字空间中的对象
    // 程序初始化时创建对象 g_v,先于 main() 等用户入口函数 // 程序结束时销毁 g_v Complex<double> g_v; Complex<double> func() { return g_v; // 调用 copy ctor 创建临时对象:[temp] <= g_v }
  • 返回动态对象

    有内存泄露

    Complex<double> func() { return *(new Complex<double>(2.3, 1.2)); // 创建动态对象 [anonymous] // 调用 copy ctor 创建临时对象:[temp] <= [anonymous] // 内存泄露:动态对象 [anonymous] 无法访问 }
  • 返回 auto_ptr 管理的动态对象

    Complex<double> func() { // 创建动态对象 [anonymous],并接受 auto_ptr 的管理 // 调用 copy ctor 创建临时对象:[temp] <= [anonymous] return *auto_ptr<Complex<double> >(new Complex<double>(2.3, 1.2)); } // 函数结束时销毁 auto_ptr 管理的动态对象 [anonymous]
函数调用表达式^

使用按值传递返回值的函数:

func(); // 语句结束时销毁临时对象 [temp] func().output(cout) << endl; // 语句结束时销毁临时对象 [temp] Complex<double> v1 = func(); // 将临时对象 [temp] 约束到变量名 v1,没有新创建对象,没有 copy,没有 assign Complex<double> v1(func()); // 和上面等价 Complex<double>& v1 = func(); // 将临时对象 [temp] 约束到引用名 v1,没有新创建对象,没有 copy,没有 assign Complex<double> v1 = Complex<double>(func()); // 调用 copy ctor: v1 <= 临时对象 [temp],语句结束时销毁 [temp] Complex<double>& v1 = Complex<double>(func()); // 调用 copy ctor: 另一个临时对象 [temp 2] <= 临时对象 [temp] // 将临时对象 [temp 2] 约束到引用名 v1 // 语句结束时销毁 [temp]
按引用传递返回值^

返回引用的问题 参考《C++ 程序设计语言》7.3 返回值、11.6 大型对象

错误用法:

  • 返回局部变量

    Complex<double>& func() { Complex<double> v1(2.3, 1.2); return v1; // 编译警告 C4172: 返回一个局部或临时变量的地址 }
  • 返回对象参数

    Complex<double>& func(Complex<double> v) { return v; // 编译警告 C4172 }
  • 返回没有解除 auto_ptr 管理的动态对象

    Complex<double>& func() { Complex<double>* v1 = new Complex<double>(2.3, 1.2); auto_ptr<Complex<double> > sp_v1(v1); // 动态对象 v1 接受 auto_ptr 的管理 return *v1; // 返回动态对象销毁后的残留值,类似野指针 } // 函数结束时销毁 auto_ptr 管理的动态对象 v1 // 错误同上 Complex<double>& func() { _s::auto_ptr<Complex<double> > sp_v1(new Complex<double>(2.3, 1.2)); return *sp_v1.get(); }

正确用法:

  • 返回静态对象

    有重入和线程安全性问题

    返回局部静态对象:

    Complex<double>& func() { static Complex<double> v1(2.3, 1.2); return v1; }

    返回全局对象:

    Complex<double> g_Complex(2.3, 1.2); Complex<double>& func() { return g_Complex; }
  • 返回引用参数

    函数调用过程没有新创建对象,没有 copy

    但如果在使用函数调用表达式时,进行初始化、赋值,会发生 copy,而非约束到变量名

    Complex<double>& func(Complex<double>& v) { return v; } Complex<double> v2 = func(v1); // 调用 copy ctor: v1 <= func(),而非约束到变量名 func(v1).output(cout); // 没有 copy
  • 成员函数返回 *this

    this 是调用成员函数时的隐含参数,*this 等价于当前对象的引用

    适合接续形式的函数调用,如标准库中:

    str_obj.append("first").append("second").append("third"); // 没有 str_obj 的 copy str_obj += "first" += "second" += third; // 同上 cout << str_v << int_v << double_v; // 没有 cout 的 copy
  • 返回动态对象

    需要用户负责释放

    Complex<double>& func() { Complex<double>* v1 = new Complex<double>(2.3, 1.2); return *v1; }
  • 返回解除 auto_ptr 管理的动态对象

    需要用户负责释放

    Complex<double>& func() { auto_ptr<Complex<double> > sp_v1(new Complex<double>(2.3, 1.2)); return *sp_v1.release(); // 解除 auto_ptr 对动态对象的管理 }

explicit 拷贝构造函数^

VC 编译错误:

C2558: 没有可用的 copy ctor 或 copy ctor 为 explicit
C2664: 不能将参数从类型 XXX 转换到 XXX ctor,因为 XXX 为 explicit

产生编译错误的情况:

  • 奇异情况

    由上面可知 VC 在这里并不进行 copy,但 copy ctor 声明 explicit 后却报错

    Complex<double> v1 = Complex<double>(1.2, 2.3); // 编译错误 C2558
  • 隐式调用 copy ctor

    Complex<double> v1(1.2, 2.3); Complex<double> v2 = v1; // 编译错误 C2558
  • 参数按值传递

    void func(Complex<double> v); func(v1); // 编译错误 C2664
  • 返回值按值传递

    Complex<double> func() { Complex<double> v1(2.3, 1.2); return v1; // 编译错误 C2558 }

没有编译错误的情况:

显式调用 copy ctor

Complex<double> v1(Complex<double>(1.2, 2.3)); // 没有 copy Complex<double> v1(1.2, 2.3); Complex<double> v2(v1);

赋值^

尽量使用 copy ctor 即初始化,而不要用 assgin

copy ctor 只涉及一个步骤:从源对象创建对象

assgin 涉及两个步骤

  1. 释放现有对象的资源,动态存储、句柄、描述字等
  2. 从源对象重新建立对象的值,不是创建对象本体
赋值函数定义^
Complex& operator=(const Complex& right) // 普通 assign,不改变右值 Complex& operator=(Complex& right) // 用于破坏性 copy 的 assign,可以改变右值

赋值示例:

Complex<double> v1; // 默认 ctor v1 = Complex<double>(1.2, 2.3); // 创建临时对象 [temp] // assign: v1 <= [temp] // 语句结束时销毁临时对象 [temp]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值