오퍼레이터, 대입연산, 함수 체이닝에 대한 수업을 들었다.
1. 연산자 오버로딩 ( 오퍼레이터 )
1.1 정의
연산자 오버로딩은 객체 지향 프로그래밍에서 다향성의 한 형태로, 연산자를 클래스 또는 구조체에서 재정의하여 클래스
객체끼리 연산을 수행할 수 있게 하는 기능이다. 즉, +, -, *, %, /와 같은 기존의 연산자를 특정 클래스의 연산자로 재정의하여 사용할 수 있게 하는 것을 의미한다. 이를 통해 객체끼리 직관적인 산술 연산이나 비교 연산을 수행할 수 있다.
( 특수 문자로 명령을 내리는 것 )
1.2 구현 방법
반환형 operator연산자(매개변수);
1.3 사용 방법
C_DATA 클래스에서 +연산자를 오버로딩하는 방법이다.
[header.h]
#pragma once
class C_DATA
{
private:
int m_nData;
public:
C_DATA() = default; // 기본 생성자
~C_DATA() = default; // 기본 소멸자
C_DATA(const C_DATA&) = delete; // 복사 생성자 를 삭제하는 구문
void setData(int nData);
int getData();
int operator+ (int nData);
};
[header.cpp]
#include "data.h"
void C_DATA::setData(int nData)
{
m_nData = nData;
}
int C_DATA::getData()
{
return m_nData;
}
int C_DATA::operator+(int nData)
{
return m_nData + nData;
}
[main.cpp]
#include <iostream>
#include "data.h"
int main()
{
C_DATA c1{};
int nResult{};
c1.setData(100);
printf("%d\n", c1.getData());
nResult = c1 + 200;
printf("%d\n", nResult);
printf("%d\n", c1 + 5);
}
- operator+ 함수는 C_DATA 객체의 멤버 변수 m_nData와 인자로 받은 nData를 더한 결과를 반환한다.
즉, 모든 오퍼레이터는 특수 문자 앞에 operator이라 적으면 되는 것이며, 기본 계요는 함수의 일종으로 operator+가 함수 이름이 되는 것이다.
- c1 + 200은 c1.operator+(200)과 동일한 의미를 가집니다. 즉, operator+는 + 연산자로 호출이 가능하다.
c1.operator+(100);
printf("%d\n", c1.getData());
c1 + 100;
printf("%d\n", c1.getData());
1.4 암기 사항
- 우리는 클래스에서 절대 오퍼레이터를 구현하지 않는다. 이유는 기존 연산자의 의미와 일관성을 유지하는 것이 중요하기에 다른 의미로 사용하면 혼란을 야기할 수 있기 때문이다. ( 어떤 의미인지 사용자는 알 방법이 없다. )
- 만약에 클래스에 오퍼레이터를 구현을 하고 싶다면 기존에 있는 연산자와 모든 것이 같게끔 기능을 구현해야 한다
즉, 기존에 있는 오퍼레이터의 기능을 완벽하게 똑같이 구현해야 한다. - 이 모든 원칙을 백 프로 만족하는 클래스의 경우 단순히 수학 연산만 하는 클래스만이 모든 조건을 만족한다.
결론, 우리는 클래스에 오퍼레이터를 만들 일은 없을 것이다.
2. 대입연산
2.1 정의
대입 연산자는 왼쪽 피연산자가 지정한 객체에 값을 저장하는 역할을 한다. 대입 연산을 이해하려면 오퍼레이터의 작동방식에 대한 기본 개념을 먼저 알아야한다.
연산자는 특정 대상에 명령을 수행하며, 수행 결과는 무조건 남는다는 원칙이 존재한다.
예를 들어 1 + 2에서 +연산자는 1에 2를 더하는 작업을 수행하고, 결과로 3이라는 값을 남긴다. 이처럼 연산자의 동작 원리를 명확히 이해하는 것이 중요하다.
2.2 대입연산의 이해
다음 예제를 봐보자
c2 = c1 = 100;
대입 연산자는 연속 대입을 보장하며, 모든 연산자는 수행 후 반드시 값을 남기는데 대입 연산자는 자신을 호출한 객체를 남기게 된다.
c1 = 100에서 c1에 100이 저장되고 그 결과 c1 자기 자신이 남게 되고, c2 = c1이 실행되면서 c1의 값이 c2에 저장이 되고, 최종적으로 c2 자신이 남게 되는데 이처럼 대입 연산자는 자기 자신을 반환하는 특성을 가지므로 연속적인 대입을 가능하게 한다.
2.3 예시 코드
// 부분은 자신을 남기지 않는 잘못된 대입연산이다. 예시로 넣어둠
[header.h]
#pragma once
#include <stdio.h>
class C_DATA
{
private:
int m_nData;
public:
C_DATA() = default;
void setData(int nData);
void print();
C_DATA& operator=(int nData);
C_DATA& operator=(const C_DATA& c) = delete; // 이 모양이 대입연산자의 모양이다.
//int operator = (int nData); // 원래 여기에 나라는 존재 자체를 남겨야함 -> 그렇기에 완벽한 것은 아님
};
[header.cpp]
#include "data.h"
void C_DATA::setData(int nData)
{
m_nData = nData;
}
void C_DATA::print()
{
printf("%d\n", m_nData);
}
C_DATA& C_DATA::operator=(int nData)
{
m_nData = nData;
return *this;
}
C_DATA& C_DATA::operator=(const C_DATA& c)
{
m_nData = c.m_nData;
return *this;
}
//int C_DATA::operator=(int nData)
//{
// m_nData = nData;
// return m_nData;
//}
[main.cpp]
#include <iostream>
#include "data.h"
int main()
{
C_DATA c1{};
C_DATA c2{};
c2 = c1 = 100;
c1.print();
c2.print();
}
*this를 반환하는 것은 객체 자신을 남겨 연속적인 대입을 가능하게 하는것이다. ( 래퍼런스 )
int 100을 c1에 대입할 때 사용자 정의 대입 연산자가 호출되며, 결과적으로 c1이 c2에 대입이 된다.(디폴트 대입연산자 생성)
즉, 복사 생성자처럼 나와 똑같은 레퍼런스가 들어가는 것이며 그 자리(래퍼런스)에 자기 자신을 남기게 되는 것이다.
이 과정에서 c1의 참조가 반환되고, c2는 이 참조를 받아 연속적으로 대입이 이뤄진다.
2.4 왜 배웠는가?
C++에서는 기본 생성자, 소멸자, 복사 생성자, 대입연산자 등이 자동으로 생성이된다.
이러한 자동 생성 요소들은 오동작 가능성을 낮추기 위해 직접 정의하거나 삭제하는 방식으로 관리를 할 필요가 있기에 배우게 된것이다.
또한 *this는 소속 관계를 남길 때 사용하지 말고 , 소속 관계를 남길 때는 네임스페이스를 사용하자.
2.5 요약
- 대입 연산자는 연속 대입을 보장해야 하므로 객체 자신을 남겨야한다 ( 반환한다 )
- C++의 클래스에서는 call by value가 자동 생성되지 않으며 기본적으로 모든 객체 자료형은 래퍼런즈(참조)로 처리된다.
- 대입 연산자는 값인지 참조인지 구분할 방법이 없기 때문에 원래의 대입 연산 동작과 동일하게 만들어야 한다.
- 대입 연산자는 객체 자신과 동일한 자료형이 참조로 들어오고 대입된 자리에는 자기 자신이 남게 된다. (*this를 남기는 것)
3. 함수 체이닝
3.1 정의
함수 체이닝은 메서드의 반환값으로 객체 자기 자신(*this)을 반환하여 여러 메서드를 연속적으로 호출할 수 있도록 하는 기법이다. 이를 통해 하나의 객체에서 여러 작업을 연속적으로 수행할 수 있다.
3.2 함수 체이닝 사용 방법
아래 예제에서는 C_DATA 클래스의 setData와 printData 메서드에서 *this를 반환하도록 하여, c.setData(100). printData();와 같이 연속적인 메서드 호출을 가능하게 한다.
[header.h]
#pragma once
#include <stdio.h>
class C_DATA
{
private:
int m_nData;
public:
C_DATA() = default;
C_DATA& setData(int nData);
C_DATA& printData();
};
[header.cpp]
#include "data.h"
C_DATA& C_DATA::setData(int nData)
{
m_nData = nData;
return *this;
}
C_DATA& C_DATA::printData()
{
printf("%d\n", m_nData);
return *this;
}
[main.cpp]
#include <iostream>
#include "data.h"
int main()
{
C_DATA c{};
c.setData(100).printData();
}
3.3 함수 체이닝의 동작 원리
setData와 printData 메서드들은 모두 C_DATA& 타입을 반환하도록 되어 있어, 메서드의 반환값이 객체 자기 자신을 가리키게 된다. 따라서 c.setData(100). printData();와 같이 체인 형태로 호출할 수 있게 되는 것이다.
- c.setData(100) 호출 시 setData는 c 객체를 반환한다.
- 이후 c.printData()가 호출되면서 값이 출력된다.
3.4 주의사항
우리는 코드를 작성할 때 하나의 명령어에 하나의 작업만 수행하는 원칙을 지켜야 하는 게 좋다.
여러 작업을 한 줄에 연속적으로 작성하면 코드의 흐름을 추적하기 어렵고 디버깅이 복잡해질 수 있습니다.
그렇기에 사용하지 말자.
'C++' 카테고리의 다른 글
[강의] 11월 7일 수업정리 (1) | 2024.11.08 |
---|---|
[강의] 11월 6일 수업정리 (0) | 2024.11.07 |
[강의] 11월 1일 수업정리 (0) | 2024.11.03 |
[강의] 10월 31일 수업정리 (2) | 2024.11.01 |
[강의] 10월 30일 수업정리 (2) | 2024.10.31 |