C++ classes and objects (7)—Friends, inner classes, anonymous objects, compiler optimization when copying objects

Table of contents

1. Youyuan

1. Definition 

2. Friend function

3. Friend class

2. Internal classes

1. Definition

2. Features:

3. Anonymous objects

4. Some compiler optimizations when copying objects

1. Comparison of pass-by-value & pass-by-reference return optimization

2. Anonymous objects return objects as functions

3. Comparison of methods of receiving return values

Summarize:


1. Youyuan

1. Definition 

In C++, a friend is a special relationship that allows one class or function to access the private members of another class. By declaring a class or function as a friend of another class, the friend class or function can directly access the private members of the class without being restricted by access permissions.

  • Friend relationships are established by using the friend keyword in the class declaration. When a class or function is declared as a friend of another class, it can access the private members of that class, including private variables and private functions.
  • Friends provide a way to break through encapsulation and sometimes provide convenience. However, friends will increase coupling and destroy encapsulation, so friends should not be used more than once.
  • Friends are divided into: friend functions and friend classes

2. Friend function

Problem: Now I try to overload operator<<, and then I find that there is no way to overload operator<< into a member function, and the usage form changes.
class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	// d1 << cout; 或者 d1.operator<<(&d1, cout); 不符合常规调用
	// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	ostream & operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

Because the output stream object of cout and the implicit this pointer are preempting the position of the first parameter. The this pointer defaults to the first parameter, which is the left operand. However, in actual use, cout needs to be the first formal parameter object to be used normally. So operator<< needs to be overloaded into global function. But it will also result in members outside the class being unable to access. In this case, friends are needed to solve the problem. operator>>Similarly. 

A friend function can directly access the private members of a class. It is an ordinary function defined outside the class and does not belong to any class. However, it needs to be declared inside the class. The friend keyword needs to be added when declaring.

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}
  • Friend functions can access private and protected members of the class, but are not member functions of the class
  • Friend function cannot be modified with const
  • Friend functions can be declared anywhere in the class definition, are not restricted by class access qualifiers
  • A function can be a friend function of multiple classes
  • The principle of calling friend functions is the same as that of ordinary functions.

3. Friend class

All member functions of a friend class can be friend functions of another class and can access non-public members of another class.
class Time
{
	friend class Date;   // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
	中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_time._hour = hour;
		_time._minute = minute;
		_time._second = second;
	}

private:
	int _year;
	int _month;
	int _day;
    Time _time;
}
  • Friend relationships are one-way and non-commutative. For example, in the above Time class and Date class, if you declare the Date class as its friend class in the Time class, you can directly access the private member variables of the Time class in the Date class, but you want to access the private member variables of the Date class in the Time class. No.
  • Friend relationships are not transitive. If C is a friend of B and B is a friend of A, it cannot mean that C is a friend of A.
  • Friend relationships cannot be inherited. I will give you a detailed introduction to the inheritance position.

2. Internal classes

1. Definition

Concept:If a class is defined inside another class, this inner class is called an inner class.
  • The inner class is an independent class. It does not belong to the outer class, and the members of the inner class cannot be accessed through the objects of the outer class.
  • The outer class does not have any superior access to the inner class.
Note:The inner class is the friend class of the outer class. The inner class can access the outer class through the object parameters of the outer class. of all members. But the outer class is not a friend of the inner class.

2. Features:

  • Inner classes can be defined as public, protected, or private in external classes.
  • Note that inner classes can directly access static members in outer classes without requiring the object/class name of the outer class.
  • sizeof(external class)=external class, has nothing to do with internal classes.

Let's first take a look at how "sizeof (external class) = external class has nothing to do with internal classes" is reflected in the code.

class A{
private:
	int h;
public:
	class B{
	private:
		int b;
	};
};

int main()
{
	A aa;
	cout << sizeof(aa) << endl;
	return 0;
}

The output shows that the object object of class A is only the size of one int member.

You can also see during debugging that the class object aa has only one member variable h.

 

Inner class B is independent from A, but is restricted by the class domain of A.

Class B can be accessed through the following code

A::B bb;

If the scope of class B becomes private, it cannot be accessed.

B is naturally a friend of A.

class A{
private:
	int h = 0;
	static int k;
public:
	class B
	{
	public:
		void Print(const A& a)
		{
			cout << k << endl;// >> OK
			cout << a.h << endl;// >> OK
		}
	};
};
int A::k = 1;

int main()
{
	A aa;
	A::B bb;
	bb.Print(aa);
	return 0;
}

The static member variable k and integer member variable h of class A are successfully accessed through class B.

At this time, we can modify the question that usesstatic members using internal classes. 

Asking for 1+2+3+...+n_Niuke Question Ba_Niuke.com (nowcoder.com)

class Solution {
    class Sum {
      public:
        Sum() {
            _sum += _i;
            _i++;
        }
    };
  private:
    static int _sum;
    static int _i;
  public:
    int Sum_Solution(int n) {
        Sum a[n];
        return _sum;
    }
};
int Solution::_sum = 0;
int Solution::_i = 1;

3. Anonymous objects

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	A aa1;

	// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa1();
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();
	A aa2(2);
	// 匿名对象在这样场景下就很好用。
	Solution().Sum_Solution(10);

	return 0;
}

Is it okay to define class objects like this?

A aa1();
  • You cannot define an object like this because the compiler cannot identify whether the following is a function declaration or an object definition.

But we can define anonymous objects in this way, and the characteristics of anonymous objects do not need to be named.

A();

But its life cycle is only this line. We can see that in the next line, it will automatically call the destructor.

Anonymous objects are very useful in such scenarios.

Solution().Sum_Solution(10);

4. Some compiler optimizations when copying objects

In the process of passing parameters and returning values, the compiler will generally do some optimizations to reduce object copies, which is still very useful in some scenarios.

1. Comparison of pass-by-value & pass-by-reference return optimization

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

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

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;

		if (this != &aa)
		{
			_a = aa._a;
		}

		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void func1(A aa)
{

}

void func2(const A& aa)
{

}

int main()
{
	A aa1 = 1; // 构造+拷贝构造 -》 优化为直接构造
	func1(aa1); // 无优化,不能跨表达式优化

	func1(2); // 构造+拷贝构造 -》 优化为直接构造
	func1(A(3)); // 构造+拷贝构造 -》 优化为直接构造

	cout << "----------------------------------" << endl;

	func2(aa1);  // 无优化
	func2(2);    // 无优化
	func2(A(3)); // 无优化


	return 0;
}

Let's take a look at the code in the main function:

  • A aa1 = 1; Here, the constructor is first called to create a temporary object, and then the copy constructor is called to copy the contents of the temporary object to aa1. However, compilers usually optimize and directly call the constructor to create aa1, avoiding unnecessary copy construction.
  • func1(aa1); The function func1 is called here, and the parameter is a copy of aa1, so the copy constructor will be called. This process is not optimized. Function func1 will call the destructor to clean up the temporary variable aa.
  • func1(2); and func1(A(3)); Both lines of code first construct a temporary object, and then call the copy constructor to copy the contents of the temporary object to the function parameters. However, the compiler will optimize and directly use the temporary object as a function parameter to avoid unnecessary copy construction.

Then comes the call to func2:

func2(aa1); func2(2); The three lines of code and func2(A(3)); all use a reference to an object as a function parameter, so there is no need to call the copy constructor, and there is no room for optimization.

  •  func2(aa1)引用传值,不需要构造和析构。

  • func2(2)构造一个临时对象,然后拷贝构造给aa。

  • func2(A(3))中 A(3) creates a temporary object, calls the constructor A(int a = 0), and outputs "A(int a)".

    • This is because in the function call func2(A(3)); , a temporary object is created, namely A(3). const A& aa means passing this temporary object to the func2 function through a constant reference. Here, no copy construction occurs because it is passed by reference.

    • So inside the func2 function, there are no additional construction or copy construction calls. When the func2 function is executed, the temporary object begins to be destroyed. At this time, the destructor ~A() is called and "~A()" is output. This is because local variables (including those constructed through temporary objects) will be destroyed after the function call ends. aa

    • Finally, the entire program execution ends, the global A(3) object will also be destroyed, and the destructor ~A() is called. Therefore, there are two destructor calls in total. Once is the temporary object destruction inside the func2 function, and the other time is the global A(3) object destruction.

2. Anonymous objects return objects as functions

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

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

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;

		if (this != &aa)
		{
			_a = aa._a;
		}

		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

A func3()
{
	A aa;
	return aa;
}
A func4()
{
	return A();//匿名对象
}

int main()
{
	func3();// 构造+拷贝构造
	A aa1 = func3();//构造+两个拷贝构造>>>优化为构造+一个拷贝构造

	func4(); // 构造+拷贝构造 -- 优化为构造
	A aa3 = func4(); // 构造+拷贝构造+拷贝构造  -- 优化为构造

	return 0;
}

By comparison, you can find the benefits of using anonymous objects in func4().​ 

In function func4() , return A(); creates an anonymous object, and the anonymous object is directly used as the return value of the function. In this way, calling func4() will get a copy of this anonymous object without the need for additional temporary objects. Therefore, in the call of func4() , this anonymous object can be constructed and returned directly, avoiding redundant object creation and copy construction.

3. Comparison of methods of receiving return values

A func3()
{
	A aa;
	return aa;
}

int main()
{
	A aa1 = func3(); // 拷贝构造+拷贝构造  -- 优化为一个拷贝构造

	cout << "****" << endl;

	A aa2;
	aa2 = func3();  // 声明和定义不在一行,不能优化

	return 0;
}

Summarize:

Object returns:

  • When receiving the return value object, try to receive it through copy construction instead of assigning it.
  • When returning an object from a function, try to return an anonymous object.

Function parameters:

  • Try to use const & to pass parameters.

Guess you like

Origin blog.csdn.net/m0_73800602/article/details/134632825