类和对象下(内部类,匿名对象,对象拷贝时的编译器优化)

发布时间:2026/8/31 16:05:30
类和对象下(内部类,匿名对象,对象拷贝时的编译器优化) 5.内部类•如果一个类定义在另一个类的内部这个内部类就叫做内部类。内部类是一个独立的类跟定义在全局相比他只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。•内部类默认是外部类的友元类。• 内部类本质也是一种封装当A类跟B类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到private/protected位置那么A类就是B类的专属内部类其他地方都用不了。• 代码示例#include iostream using namespace std; class Outer { public: // 内部类定义在外部类内部 class Inner { public: void show() { cout Inner class method endl; } }; void useInner() { Inner in; in.show(); } }; int main() { Outer::Inner obj; // 通过外部类访问内部类 obj.show(); return 0; }• 内部类默认是外部类的友元类因此内部类可以直接访问外部类的私有成员#include iostream using namespace std; class Outer { private: int value 100; public: class Inner { public: void accessOuter(Outer o) { // 内部类作为友元可直接访问外部类的私有成员 cout Outer private value: o.value endl; } }; }; int main() { Outer o; Outer::Inner in; in.accessOuter(o); return 0; }6.匿名对象• 用类型(实参)定义出来的对象叫做匿名对象匿名对象的特点不用取名字例如A();相比之前我们定义的类型对象名(实参)定义出来的叫有名对象。• 匿名对象生命周期只在当前一行一般临时定义一个对象当前用一下即可就可以定义匿名对象。• 代码示例#include iostream using namespace std; class A { public: A(int x 0) : _x(x) { cout A(int) endl; } ~A() { cout ~A() endl; } void show() const { cout x _x endl; } private: int _x; }; // 匿名对象作为函数参数 void func(const A a) { cout func called endl; } int main() { // 匿名对象A(10) 没有名字生命周期仅限当前这一行 A(10).show(); // 临时调用成员函数行末即析构 func(A(20)); // 匿名对象作为函数实参函数调用结束后析构 cout main continues endl; return 0; }• 上例中A(10)和A(20)都是匿名对象它们没有对象名生命周期只持续到当前语句结束。当该行执行完毕后编译器会立即调用析构函数释放资源因此匿名对象适合“临时用一下”的场景。• 下面通过一个对比示例直观展示有名对象和匿名对象在生命周期与资源释放时机上的差异#include iostream using namespace std; class A { public: A(int x 0) : _x(x) { cout A(int) 构造 _x _x endl; } ~A() { cout ~A() 析构 _x _x endl; } void show() const { cout show() _x _x endl; } private: int _x; }; void func(const A a) { cout func 调用中 endl; } int main() { cout --- 有名对象 --- endl; A named(1); // 有名对象生命周期从定义处开始 named.show(); // 可以多次使用 named.show(); // 作用域结束前一直有效 cout --- 匿名对象 --- endl; A(2).show(); // 匿名对象仅当前这一行有效行末立即析构 func(A(3)); // 匿名对象作为实参函数调用结束后析构 cout --- main 结束前 --- endl; // 此时 named 仍存活直到 main 结束才析构 return 0; } // 输出结果 // --- 有名对象 --- // A(int) 构造 _x 1 // show() _x 1 // show() _x 1 // --- 匿名对象 --- // A(int) 构造 _x 2 // show() _x 2 // ~A() 析构 _x 2 // 匿名对象 A(2) 行末立即析构 // A(int) 构造 _x 3 // func 调用中 // ~A() 析构 _x 3 // 匿名对象 A(3) 函数调用结束后析构 // --- main 结束前 --- // ~A() 析构 _x 1 // 有名对象 named 在 main 结束时才析构• 对比可见有名对象named从定义处开始存活直到其作用域这里是 main 函数结束才调用析构函数释放资源期间可以反复使用而匿名对象A(2)、A(3)没有名字生命周期只持续到当前语句结束行末或函数调用结束后编译器会立即调用析构函数释放资源。因此如果只是临时用一下对象使用匿名对象可以更及时地释放资源、减少内存占用。7.对象拷贝时的编译器优化• 现代编译器会为了尽可能提高程序的效率在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。• 如何优化 C 标准并没有严格规定各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化有些更新更激进的编译器还会进行跨行跨表达式的合并优化。• 不同编译器对拷贝优化的典型行为对比如下编译器默认优化行为关闭优化的编译选项GCC默认开启拷贝省略copy elision对连续表达式中的拷贝进行合并优化在 C17 下对按值返回的临时对象保证省略拷贝。-fno-elide-constructorsClang默认行为与 GCC 基本一致开启拷贝省略对临时对象和按值返回的优化较为积极。-fno-elide-constructorsMSVC默认开启返回值优化RVO和命名返回值优化NRVO但具体行为受 /O1、/O2 等优化级别影响。/Zc:noElideConstructors或使用 /Od 关闭优化• 下面通过一个具体示例直观对比开启优化默认和关闭优化-fno-elide-constructors时传参和返回值过程中的拷贝次数差异。#include iostream using namespace std; class A { public: A(int x 0) : _x(x) { cout A(int) endl; } A(const A a) : _x(a._x) { cout A(const A) 拷贝构造 endl; } ~A() { cout ~A() endl; } int _x; }; // 传参按值接收会触发拷贝构造 void func(A a) { cout func 内 _x a._x endl; } // 返回值返回局部对象可能触发拷贝构造 A getA() { A tmp(10); return tmp; } int main() { cout --- 传参 --- endl; A obj1(1); func(obj1); // 把 obj1 拷贝给形参 a cout --- 返回值 --- endl; A obj2 getA(); // 把 getA 的返回值拷贝给 obj2 cout --- main 结束 --- endl; return 0; }• 编译运行对比# 默认开启优化拷贝省略生效 g test.cpp -o test_default ./test_default 关闭拷贝优化 g test.cpp -fno-elide-constructors -o test_noelide ./test_noelide• 开启优化默认时编译器会尽量省略中间拷贝输出大致如下--- 传参 --- A(int) func 内 _x 1 --- 返回值 --- A(int) --- main 结束 --- ~A() ~A() ~A()• 关闭优化-fno-elide-constructors时传参和返回值过程中的拷贝不会被省略输出大致如下--- 传参 --- A(int) A(const A) 拷贝构造 func 内 _x 1 --- 返回值 --- A(int) A(const A) 拷贝构造 --- main 结束 --- ~A() ~A() ~A() ~A()• 对比可见开启优化后传参时省略了形参的拷贝构造返回值时也省略了临时对象的拷贝构造整体少调用了两次拷贝构造析构次数也随之减少程序运行效率更高。• Linux 下可以将上面代码拷贝到 test.cpp 文件编译时用 g test.cpp -fno-elide-constructors 的方式关闭构造相关的优化。