吃透C++类和对象(下):内部类、匿名对象及编译器优化的深度解析

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》

《C++入门到进阶&自我学习过程记录》

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

一、内部类

二、匿名对象

三、对象拷贝时的编译器优化

1、传值传参优化

2、传值返回优化

结束语


一、内部类

如果一个类定义在另一个类的内部 ,这个内部类就叫做内部类 。内部类是一个独立的类 ,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类

cpp 复制代码
class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B
	{
	public:
		void foo(const A& a)
		{
		};
	};
};

int main()
{
	cout << sizeof(A) << endl; //为4,说明A类并不包含B类这个内部类
	                           //只是B类受到了A类类域的限制,仍然是一个独立的类
	return 0;
}

虽然B类中也有类型为 int 的成员变量,但A类的大小并不是8而是4,说明A类并不包含B类这个内部类 ,只是B类受到了A类类域的限制和访问限定符限制,仍然是一个独立的类

所以当我们想要访问B类时是不能直接访问的,而是要通过**域作用限定符 ::**来限定A类:

cpp 复制代码
A::B b;

内部类默认 是外部类的友元类

cpp 复制代码
class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl; //OK B默认是A的友元,能使用A的所有成员
			cout << a._h << endl; //OK
		};
	private:
	int _b1;
	};
};

int A::_k = 1;

int main()
{
	A aa;
	A::B b; //B作为内部类,受到A类类域的限制和访问限定符限制
	        //所以要用域作用限定符 :: 才能访问B类
	b.foo(aa);
	return 0;
}

但需要注意的是:内部类B默认是外部类A的友元,但A并不是B的友元,是无法访问B中的私有成员变量的:

cpp 复制代码
class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl; //OK B默认是A的友元,能使用A的所有成员
			cout << a._h << endl; //OK
		};
	private:
		int _b1;
	};

	A(int aa = 0)
	{
		B::_b1++;//error C2597: 对非静态成员"A::B::_b1"的非法引用
	}
	//B默认是A的友元,能使用A的所有成员;但A不是B的友元,无法访问B的私有成员变量
};

内部类本质 也是一种封装 ,当A类跟B类紧密关联A类实现出来主要就是给B类使用 ,那么可以考虑把A类设计为B的内部类,如果放到private / protected 位置,那么A类就是B类的专属内部类 ,其他地方都用不了

二、匿名对象

类型(实参) 定义出来的对象叫做匿名对象 ,相比之前我们定义的**类型 对象名(实参)**定义出来的叫有名对象。

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
private:
	int _a;
};

int main()
{
	A aa1;//有名对象

	//A aa1();//error C2365: "aa1": 重定义;以前的定义是"数据变量"
	//之前我们就讲过不能这么定义对象,因为编译器无法识别是一个函数声明,还是对象定义

	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字
	A();//匿名对象
	A(1);
	return 0;
}

匿名对象生命周期 只在当前一行 ,一般临时定义一个对象 当前用一下即可,就可以定义匿名对象

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A aa1;//有名对象

	//但是我们可以这么定义匿名对象,匿名对象的特点不用取名字
	A();//匿名对象
	A(1);
	//匿名对象的生命周期只有这一行,我们可以调试看到下一行他就会自动调用析构函数
	return 0;
}

三、对象拷贝时的编译器优化

现代编译器会为了尽可能提高程序的效率 ,在不影响正确性的情况下会尽可能减少一些传参传返回值 的过程中可以省略的拷贝
如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。

1、传值传参优化

在前面文章吃透C++类和对象(下):类型转换、static成员及友元的深度解析中的类型转换以及传值传参时就讲过:不管是类型转换还是实参传给形参都会调用拷贝构造,类型转换是先构造产生一个临时对象再调用拷贝构造函数拷贝构造对象;而只要是传值传参都需要调用拷贝构造。

但是如果这个 构造 + 拷贝构造连续发生 的,则编译器会对其直接优化成直接构造

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

int main()
{
    A aa1; //调用构造初始化对象aa1
    f1(aa1); //传值传参会调用拷贝构造,如果要减少拷贝可以进行引用传参
    //一般只有在连续步骤才能进行优化,这里是分步进行的,所以并没有优化
	return 0;
}

这里是因为构造和拷贝构造是分步进行的并不是连续的,编译器没有对其进行优化,仍是调用了拷贝构造函数。

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

int main()
{
	// 隐式类型,连续构造 + 拷贝构造 -> 优化为直接构造 
	f1(1);
	// 一个表达式中,连续构造 + 拷贝构造 -> 优化为一个构造 
	f1(A(2));

	return 0;
}

2、传值返回优化

之前学习中我们知道,**传值返回会调用拷贝构造。**通过调用拷贝构造生成一个临时对象,当出了函数后局部变量会被销毁,而临时对象才是作为函数调用表达式的返回值。

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

	A& operator=(const A& aa) //赋值运算符重载
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

private:
	int _a1 = 1;
};

A f2()
{
	A aa;
	return aa; //传值返回
}

int main()
{
	//传值返回
	// 无优化(vs2019 debug) 
    // 一些编译器会优化得更厉害,将构造的局部对象和拷贝构造的临时对象优化为直接构造(vs2022 debug) 
	f2();
	cout << endl;

	// 返回时一个表达式中,连续拷贝构造 + 拷贝构造 -> 优化一个拷贝构造(vs2019 debug) 
	// 一些编译器会优化得更厉害,进行跨行合并优化,
	// 将构造的局部对象 aa 、拷贝的临时对象和接收返回值对象 aa2 优化为一个直接构造(vs2022 debug)
	A aa2 = f2();
	cout << endl;

	// 一个表达式中,开始构造,中间拷贝构造 + 赋值重载 -> 无法优化(vs2019 debug)
	// 一些编译器会优化得更厉害,进行跨行合并优化,
	// 将构造的局部对象 aa 和拷贝临时对象合并为一个直接构造(vs2022 debug) 
	aa1 = f2(); //本质还是因为不管是构造还是拷贝构造都属于构造这一类,而赋值重载并不属于这一类所以不能优化
	cout << endl;
	return 0;
}

结束语

到此,内部类、匿名对象和编译器优化就讲解完了,而类和对象的讲解也就彻底告一段落了,从最开始封装、实例化和 this 指针的讲解,到六大默认成员函数,最后到一些类和对象琐碎的补充知识点,我们用了很多文章、花了非常大的篇幅对类和对象进行讲解就是因为类和对象是C++极其重要的内容,是需要非常熟练地掌握。希望这篇文章对大家学习C++能有所帮助!

相关推荐
情缘晓梦.2 小时前
C++ 内存管理
开发语言·jvm·c++
恒者走天下2 小时前
研一、大一大二学计算机应该怎么规划
c++
我是一只小青蛙8883 小时前
Windows下MATLAB与C++混合编程实战
c++
玖釉-3 小时前
[Vulkan 学习之路] 11 - 组装流水线:固定功能阶段 (Fixed Functions)
c++·windows·图形渲染
f狐0狸x3 小时前
【C++修炼之路】C++string的用法
开发语言·c++·string
阿豪只会阿巴3 小时前
【多喝热水系列】从零开始的ROS2之旅——Day9 初识话题通信:基本命令
c++·笔记·python·ubuntu·ros2
码小猿的CPP工坊4 小时前
C++弱引用智能指针std::weak_ptr使用介绍
开发语言·c++
暮色_年华4 小时前
随想3:关于语音采集线程 使用 CFS 调度或者 SCHED_FIFO 的思考
c++
Flash.kkl4 小时前
Linux——线程的同步和互斥
linux·开发语言·c++