-
[C++] 대입연산자 (assignment operator)C++ 2023. 1. 9. 16:15
우선 복사생성자와 대입연산자를 헷갈리면 안됨
두 개가 호출되는 시점 차이가 있음 ㅇㅇ
대입연산자는
이미 생성된 두 객체 간 대입연산이 진행될때,
기존에 생성된 두 객체 간의 대입연산 시에 호출되는거임
(복사생성자와 호출시점 비교)
operator=(Point& ref)
이 형식으로 호출되는 거임.
연산자 오버로딩의 한 종류로오버로딩 중에서 가장 중요한 놈임!
클래스의 대입연산자는 반드시 정의해주어야한다!
대입연산자는 복사생성자와 동일한 구석이 있다.
[대입연산자와 복사생성자의 공통점]
1) 정의하지 않으면 디폴트 가 자동으로 삽입된다
2) 디폴트 는 얕은 복사를 한다 ( 깊은 복사 X )
3) 연산자 내에서 동적할당을 한다면, 깊은 복사가 필요하다면, 직접 정의해야 한다!
이와 같이 대입연산자와 복사생성자는 99% 비슷하지만 호출시점에 차이가 있다.
[호출 시점의 차이 (이게 개중요한거임)]
복사생성자객체 생성과 동시에 기존 객체로 초기화 할때 호출됨
Point p1 = p2; Point p1(p2);
대입연산자기존 객체를 기존 객체로 초기화할때 호출됨
p1 = p2;
[default 대입연산자]
(대입연산자를 정의하지 않았을때)
자식 클래스에 대입연산자를 명시 하지 않으면 default 대입연산자가 자동으로 삽입된다.
그리고 이때 삽입되는 default 대입연산자는 얕은복사를 한다
또한 default 대입연산자에는 부모클래스 대입연산자를 호출하는 코드가 있다
[대입연산자는 생성자가 아니다!]
대입연산자를 직접 정의했을때
부모클래스의 대입연산자가 명시되어 있지 않으면 진짜로 안부른다
즉, 컴파일러가 자동으로 추가해주지 않는다!
class First { private: int num1, num2; public: First(int n1 = 0, int n2 = 0):num1(n1), num2(n2) { // empty } First& operator=(First& ref) { num1 = ref.num1; num2 = ref.num2; return *this; } };class Second : public First { private: int num3, num4; public: Second(int n1=0, int n2=0,int n3 = 0, int n4 = 0) : First(n1,n2), num3(n3), num4(n4) { // emtpy } // Second 대입연산자 직접 정의! Second& operator=(Second& ref) { num3 = ref.num3; num4 = ref.num4; return *this; } };이렇게 First 클래스를 상속하는 Second 클래스가 있을때
Second& operator=(Second& ref) { num3 = ref.num3; num4 = ref.num4; return *this; }Second 클래스의 대입연산자에서 First 클래스의 대입연산자를 명시해놓지 않으면 진짜로 안부름 ㅇㅇ...
그래서 아래와 같이 직접 불러줘야함ㅋㅋ
Second& operator=(Second& ref) { // 대입연산자 직접 정의해줬으므로 // 부모클래스 대입연산자도 직접 명시해주어야함! First::operator=(ref); num3 = ref.num3; num4 = ref.num4; return *this; }생성자와 이 부분이 다른거임
우리가 자식클래스의 생성자를 직접 정의했을때,
부모클래스 생성자를 명시해놓지 않아도
알아서 컴파일러는 부모클래스의 기초생성자(void형) 을 추가하여
호출시켜주지만 대입연산자는 아니다
따라서 자식클래스에 대입연산자를 직접 정의했으면
꼭 무조건 명시적으로 부모클래스 대입연산자를 적어줘야한다!
여기서 한가지 짚고 넘어가야할게 있다면,
First::operator=(ref) 일때 ref는 Second& 형이고
First의 operator= 매개변수형은 First& 형인데 어케 인자 전달이 가능할까
다형성 ㅇㅇ 이건 포인터뿐만 아니라 참조자에도 적용되므로
작은놈 큰놈 참조 ㄱㄴ하니까 ㄱㄴ한거임
[멤버이니셜라이저 성능평가]
이제 생성자에서 이니셜라이저 쓰는게 왜 성능이 더 좋은지 알 수 있다
class AAA { private: int num; public: // 기본생성자 AAA(int n = 0) : num(n) { //empty } // 복사생성자 AAA(const AAA& ref) { num = ref.num; } // 대입연산자 AAA& operator=(const AAA& ref) { num = ref.num; return *this; } };클래스 BBB와 CCC에서 사용될 AAA 클래스
class BBB { private: AAA mem; public: // 복사생성자 BBB(const AAA& ref) : mem(ref) { // empty } };BBB 클래스는 복사생성자에 멤버이니셜라이저를 사용했다.
따라서 복사생성자 호출 시 mem(ref) 복사생성자 하나만 호출된다
class CCC { private: AAA mem; public: CCC(const AAA& ref) { mem = ref; } };CCC 클래스는 복사생성자에 멤버이니셜라이저를 사용하지 않았다.
그럼 mem = ref 라는 코드는 선언과 초기화를 각각 별도의 문장에서 진행하게 된다
// 1. 기본생성자로 객체생성 AAA mem; // 2. 대입연산자 호출하여 초기화! mem.operator=(ref)바로 이렇게 말이다
따라서 멤버이니셜라이저를 사용하면 작업횟수가 한 번으로 줄어들어 더 성능이 좋아지는 것이다.
멤버이니셜라이저를 최대한 사용하도록 노력하자 ㅇㅇ
'C++' 카테고리의 다른 글
[C++] Iterator(반복자) (1) 2023.02.20 [C++] 생성자에 배열 전달 방법 3가지 (2) 2023.01.15 [c++] 연산자 오버로딩 (2) (cout cin 재정의하기) (0) 2023.01.08 [c++] 연산자 오버로딩 (operator overloading) (1) (0) 2023.01.08 [c++] 순수가상함수와 추상클래스 관련 특징 (0) 2023.01.06