c++类型--类型行为控制

📅 发布时间:2026/10/21 10:35:37
c++类型--类型行为控制
1.类型行为1.1.概述c类型需要把握的关键行为是类型构造类型析构类型赋值。以下是一个例子后续基于此例子具体分析上述行为。class Norm1 { public: Norm1() { printf(Norm1()\n); } Norm1(char i):m_i(i) { printf(Norm1(int)_%c\n, m_i); } Norm1(const Norm1 a):m_i(a.m_i){ printf(Norm1(const Norm1)_%c\n, m_i); } Norm1(Norm1 a):m_i(a.m_i) { printf(Norm1(Norm1)_%c\n, m_i); } Norm1 operator(const Norm1 a) { m_i a.m_i; printf(Norm1(const Norm1)_%c\n, m_i); return *this; } Norm1 operator(Norm1 a) { m_i a.m_i; printf(Norm1(Norm1)_%c\n, m_i); return *this; } ~Norm1() { printf(~Norm1()_%c\n, m_i); } private: char m_id; }; class Norm2 { public: Norm2() { printf(Norm2()\n); } Norm2(char i):m_i(i) { printf(Norm2(int)_%c\n, m_i); } Norm2(const Norm2 a):m_i(a.m_i){ printf(Norm2(const Norm2)_%c\n, m_i); } Norm2(Norm2 a):m_i(a.m_i) { printf(Norm2(Norm2)_%c\n, m_i); } Norm2 operator(const Norm2 a) { m_i a.m_i; printf(Norm2(const Norm2)_%c\n, m_i); return *this; } Norm2 operator(Norm2 a) { m_i a.m_i; printf(Norm2(Norm2)_%c\n, m_i); return *this; } ~Norm2() { printf(~Norm2()_%c\n, m_i); } private: char m_id; }; class Base { public: Base():m_n2(b),m_n1(b) { printf(Base()\n); } Base(char i):m_n2(i),m_n1(i) { printf(Base(int)\n); } Base(const Base a):m_n2(a.m_n2), m_n1(a.m_n1){ printf(Base(const Base)\n); } Base(Base a):m_n2(std::move(a.m_n2)),m_n1(std::move(a.m_n1)) { printf(Base(Base)\n); } Base operator(const Base a) { m_n1 a.m_n1; m_n2 a.m_n2; printf(Base(const Base)\n); return *this; } Base operator(Base a) { m_n1 std::move(a.m_n1); m_n2 std::move(a.m_n2); printf(Base(Base)\n); return *this; } ~Base() { printf(~Base()\n); } private: Norm1 m_n1; Norm2 m_n2; }; class A : public Base { public: A():Base(b),m_n1(a),m_n2(a) { printf(A()\n); } A(char i): Base(i),m_n1(i),m_n2(i) { printf(A(int)\n); } A(const A a):Base(a),m_n1(a.m_n1),m_n2(a.m_n2){ printf(A(const A)\n); } A(A a):Base(a),m_n1(std::move(a.m_n1)),m_n2(std::move(a.m_n2)) { printf(A(A)\n); } A operator(const A a) { Base::operator(a); m_n1 a.m_n1; m_n2 a.m_n2; printf(A(const A)\n); return *this; } A operator(A a) { Base::operator(std::move(a)); m_n1 std::move(a.m_n1); m_n2 std::move(a.m_n2); printf(A(A)\n); return *this; } ~A() { printf(~A()\n); } private: Norm1 m_n1; Norm2 m_n2; };1.2.关于类的构造1.2.1.构造函数执行顺序#include test.h int main() { A a1; return 0; }上述代码编译运行后的输出这是因为对任意一个c类型A其构造顺序为基类构造。成员变量按类内出现顺序构造。类自身的构造函数体。说明上述定义一个递归式的定义。构造函数的构造列表中可以显式指定如何执行基类构造成员字段构造。未指定时对应项采用默认构造。1.2.2.默认构造定义不需要传递任何参数即可执行的构造函数称为默认构造。class A1 { public: A1(){} }; class A2 { public: A2(int i 0){} };上述A1()算默认构造A2(int i 0)也算默认构造。因为这些构造函数可以不用传递任何实参即可执行。如A1 a1;A2 a2;将分别执行上述构造函数。合成版本默认构造函数当一个c类型定义中用户没有自定义任何构造函数时编译器默认为其提供一个默认构造函数。#include iostream class A { public: A(const A) { printf(A(const A)\n); } }; int main() { A a;// err因为已经存在自定义构造函数无合成版本 return 0; }默认构造函数可行的条件基类支持默认构造若基类默认构造用private限定派生类将无权限调用会阻止默认构造的合成。。类的所有成员变量支持默认构造。#include iostream class T { public: T(int){} }; class A { private: T t; }; int main() { // err因为类的成员t对A来说不支持默认构造 A a; return 0; }合成的默认构造的行为基类默认构造。成员变量按类内出现顺序执行其默认构造类自身的构造函数体合成下函数体为空上述定义是一个递归式的定义。显式请求合成版本当用户自定义了构造函数而我们依然希望编译器提供合成版本默认构造时可以用下述方式#include iostream class A { public: A(int i){ printf(A(int)\n); } A()default; }; int main() { A a; return 0; }显示请求只有在满足默认构造可行条件下才能编译通过。1.2.3.拷贝构造拷贝构造属于构造的范畴。由于使用比较频繁故单独拿出来再说一遍。拷贝构造执行顺序拷贝构造依然属于构造所以遵循构造执行顺序。什么样的构造是拷贝构造只需要传递类型自身一个实例即可执行的构造函数称为拷贝构造。class A1 { public: A1(const A1 a){} A1() default; }; class A2 { public: A2(const A2 a, int i 0){} A2() default; };上述A1(const A1 a)算拷贝构造A2(const A2 a, int i 0)也算拷贝构造。因为这些构造函数只需要传递类型自身一个实例即可执行。如A1 a1;A2 a11(a1);A2 a2;A2 a22(a2);中A2 a11(a1)和A2 a22(a2)将分别执行上述拷贝构造。因为拷贝构造也属于构造函数所以定义了拷贝构造下若希望提供合成版本默认构造函数必须采用A1()default这样的方式显式请求。合成版本拷贝构造–to doa. 对于拷贝构造不同于默认构造。默认构造是只要存在任何构造函数就不会提供编译器合成的默认构造版本。拷贝构造允许存在其他构造函数只要用户没有定义只接收自身实例即可执行的构造函数编译器就会提供合成的拷贝构造。b.合成版本行为表现#include test.h class B : public Base { public: private: Norm1 m_n1; Norm2 m_n2; }; int main() { B b1; B b2(b1); return 0; }上述输出中红色对于合成版本默认构造。蓝色对应合成版本拷贝构造。不过此时对基类执行的是基类的拷贝构造。对成员变量执行的也是其拷贝构造。类自身的拷贝构造函数体是空的。需要注意合并拷贝构造和显式定义的拷贝构造的区别显式定义下基类成员如何拷贝依赖构造函数初始值列表中的指定未显式下将采用类型的默认构造而非拷贝构造c.合成的拷贝构造函数可行的前提是(1). 基类支持拷贝构造(2). 类的所有成员变量支持拷贝构造#include iostream class T { public: T(){} private: T(T t){} }; class A { public: private: T t; }; int main() { A a; A a2(a);// err因为A的成员t不支持拷贝构造。 return 0; }(3).显式请求合成版本拷贝构造严格来说没有显式请求的必要因为只要用户没有通过只接收类型自身实例即可执行的构造函数编译器总是会提供合成的拷贝构造。4.关于类的赋值4.1.赋值顺序int main() { A a1; A a2; a2 a1; return 0; }上述紫红色的123对应a2a1;值得注意的是赋值运算符不像构造函数析构函数。像构造函数无论如何总是会对基类执行构造对类的成员执行构造。类的定义者只能通过构造初始化列表决定采用那个版本的基类构造成员构造。但基类构造成员构造作为两个执行阶段无论针对合成版本构造函数还是用户自定义的构造函数总是存在的。对赋值如果用户自定义了赋值运算符则基类部分赋值成员赋值全部依赖自定义函数体的实现。上述紫红色包含123的原因是我们提供的自定义赋值运算符里面分别处理了基类赋值成员赋值。如果将A的赋值运算符修改为Aoperator(constAa){printf(A(const A)\n);return*this;}再次执行intmain(){A a1;A a2;a2a1;return0;}输出将为此时不会对基类成员执行赋值处理4.2.默认赋值运算符(1).定义class A1 { public: A1 operator(const A1 a) { ... } };作为赋值运算符a. 返回值必须是类型自身的引用类型。b. 只能接收一个形参。c. 传递类型自身实例时可以匹配到形参。所以下述也是合法的自定义赋值运算符。class A1 { public: A1 operator(A1 a) { ... } };(2).合成版本a.何时合成a.1. 用户没有自定义赋值运算符时a.2. 类型没有定义移动拷贝构造函数a.3. 类的成员基类的赋值运算符存在b.合成版本行为表现#include test.h class B : public A { private: Norm1 n1; }; int main() { B b1; B b2 b1; return 0; }上述紫红色12对应B的合成版本赋值运算符。合成的赋值运算符(1). 通过基类赋值运算符完成基类赋值。(2). 对每个成员执行其赋值运算符完成成员赋值。c.合成版本可行的前提是(1). 基类支持赋值运算符(2). 类的所有成员变量支持赋值运算符(3). 类型不含移动拷贝构造函数实际验证类型包含移动拷贝构造时无法合成赋值运算符#include iostream class T { public: T(){} private: T operator(T){} }; class A { public: private: T t; }; int main() { A a1; A a2; a2 a1;// err因为A的成员t不支持赋值运算符t的赋值运算符对A不可见 return 0; }(3).显式请求合成版本假设类型是Aclass A{public:Aoperator(constA)default;Aoperator(A)default;};4.关于类的析构4.1.析构顺序#include test.h int main() { A a; return 0; }上述蓝色123对应A a;中实例a的析构过程。析构函数执行顺序为(1).类自身的析构函数体(2).成员变量按类内出现顺序反向析构(3).基类析构上述定义一个递归式的定义。不像构造函数析构函数没有初始值列表这样的东西。每个类型的析构函数也不存在多个版本。4.2.合成版本a.何时合成只要用户没有自定义析构函数编译器就会合成析构函数。b.合成版本行为表现(1). 类自身的析构函数体。合成版本函数体为空。(2). 成员变量按类内出现顺序反向析构(3). 基类析构上述定义一个递归式的定义。c.合成版本可行前提(1). 类的基类支持析构(2). 类的成员支持析构4.3.显式请求合成版本析构函数严格来说没有显式请求的必要因为只要用户没有定义析构函数编译器总是会提供合成的析构函数。补充构造c中考虑多重继承虚拟继承各类情况。一个类型的构造函数经过类型自身定义和编译器处理后最终会以以下形态展开构造行为若继承体系上存在虚基类虚基类按从左到右从深到浅顺序被逐个构造所有类直接继承的非虚基类按声明顺序被构造完成基类构造后若类存在虚函数会正确设置虚表指针完成基类构造后若类直接继承了虚基类也会正确设置指向虚基类指针成员按声明顺序构造执行类的构造函数体注意点在构造过程中执行每个类型的构造函数时this指针均会被调节保证指向类对象中此子对象起始位置。构造过程析构过程执行虚函数调用不会产生多态行为。从意义上讲因为构造顺序中先构造基类基类构造时派生部分处于未初始化状态禁止多态是安全保护。析构顺序中基类析构时派生部分由于已被析构处理禁止多态也是安全保护。继承体系中特定虚基类无论出现多少次由于整个对象只有一个虚基类子对象其构造只会在一开始执行一次。析构析构行为执行类的析构函数体成员函数按声明相反顺序析构基类析构顺序和构造相反虚基类析构顺序和构造相反