전문가를 위한 C++

2025/03/08 ch.09 클래스와 객체 완전 정복

딴짓거리 2025. 3. 8. 17:55

 

const 기반 오버로딩

똑같은 함수이지만 const가 필요할때가 있고 필요없을 때가 있다

이럴경우 오버로딩으로 똑같은 함수를 const만 뗏다 붙였다 해서 오버로딩 해버리면 된다

근데 진짜 코드가 똑같으면 이렇게 하는것 보다 const_cast()를 사용하는 편이 효율적이다

이 경우 세번째 줄의 코드는 123이 double로 형변환 되어 setValue를 호출한다.

명시적으로 일을 방지하려면

double 말고 다른 형으로는 호출되지 않도록 명시해주면 된다.

 

일반적으로 클래스의 메서드는 그 클래스의 임시 인스턴스나 정식 인스턴스에 대해 호출 할 수 있다.

특정한 메서드를 호출할 수 있는 인스턴스의 종류를 명시적으로 지정할 수 있다.

정식 인스턴스에서만 호출할 수 있게 만들려면 &를 붙이고(레퍼런스를 반환하려면 당연히 임시 객체면 안됨)

임시 인스턴스에서만 호출 할 수 있게 만들려면 &&를 붙인다.(&&는 리터럴과 같은 임시 객체를 레퍼런스로 쓸 때 붙이는 거)

 

9.3.4 인라인 메서드

실제로 코드 블록을 호출하도록 구현하지 않고 해당 함수를 호출하는 자리에 메서드 본문을 집어 넣도록 추런하는 기능

inline 키워드를 붙인다고 해서 무조건 인라이닝 해주는 것이 아니라 컴파일러한테 힌트를 주는 것일 뿐이다. 컴파일러가 보이게 성능이 더 나빠질 것 같으면 무시해버림

 

일반적으로 메서드 정의 코드를 클래스 정의 코드에 직접 작성하면 그 메서드에 직접 inline을 붙이지 않더라도 내부적으로 인라인 메서드로 지정한다.

하지만 C++20에서 모듈로부터 익스포트한 클래스는 그렇지 않으므로 inline 키워드를 직접 붙여줘야 한다.

 

9.3.5 디폴트 인수

함수나 메서드의 프로토타입에 매개변수의 디폴트값을 지정하는 기능

사용자가 인수를 직접 지정하면 디폴트값은 무시됨. 지정하지 않으면 디폴트값이 적용됨

메개변수의 디폴트값을 지정할때는 반드시 오른쪽 끝에 있는 매개변수부터 시작해서 중간에 건너뛰지 않고 연속적으로 나열해야 함

 

9.4 데이터 멤버

9.4.1 static 데이터 멤버

클래스마다 똑같은 변수가 필요하고 그 값을 공유해야 할 때

 

##

C++17 부터 static 데이터 멤버도 인라인으로 선언할 수 있다. 소스 파일에서 공간을 따로 할당하지 않아도 된다.

static 멤버 변수는 클래스의 모든 객체가 공유하므로 헤더 파일에서 정의해버리면 중복 정의 오류가 생긴다

그래서 헤더파일에서 선언만 하고 소스파일에서 정의했어야 했는데 C++17부터는 inline으로 그럴 필요가 없어졌다는 것

 

클래스 메서드 안에서는 static 데이터 멤버를 일반 데이터 멤버처럼 사용해도 됨

클래스 밖에서도 멤버 static 변수에 접근할 수 있는데 public 으로 선언 후 접근 지정자로 클래스 이름 붙여주면 됨 <- 바람직하지 않음

 

9.5 중첩 클래스

클래스 정의에는 데이터 멤버와 멤버 함수 뿐만 아니라 중첩 클래스, 구조체, 타입 앨리어스, 열거타입도 선언 가능

선언한 모든 것은 해당 클래스의 스코프로 제한되니 주의

public으로 선언한 멤버를 외부에서 접근할 때는 :: 스코프 지정 연산자 붙여야 한다

 

9.7 연산자 오버로딩

 

클래스 끼리의 덧셈을 예로 들어본다

덧셈을 함수형태로 구현해도 물론 문제없다

하지만 매번 함수 형태로 호출해줘야하고 보기 거슬린다

덧셈 연산자를 오버로딩하여 일반적인 실수의 사칙연산처럼 +로 클래스간 덧셈을 수행할 수 있게 되었음

매우 편리함

# 덧셈 오버로딩을 할때 꼭 같은 클래스의 객체만 받으리라는 법은 없다. 다른 객체를 받아오는 기능으로 구현해도 되고 리턴을 전혀 다른 클래스나 값으로 만들어버려도 상관은 없다. 하지만 최소놀람 원칙에 따라 자연스러운 타입으로 구현하자

"필요한 기능에 크나큰 깜짝 놀래킬만한 요소가 있다면 해당 기능을 다시 설계할 필요가 있을 수 있다"

 

# 암묵적 변환

 덧셈 오버로딩은  str + str이지만 변환 생성자를 구현해놓았다면?

적절한 타입으로 변환되어 자동으로 덧셈이 수행된다

심지어 임시객체가 만들어지면서까지 처리된다

 

하지만 결국 임시객체가 생성되는것이기 때문에 성능이 떨어지므로

저런 경우를 대비하여 리터럴이나 상수값에 대한 연산자 오버로딩을 구현해 놓는 방법도 있다

 

연산자 오버로딩은 결국 클래스 객체의 메서드이다

즉 교환법칙이 성립하지 않는다

하지만 연산자 오버로딩을 글로벌 함수로 만들어버리면 교환법칙까지 성립하게 만들 수 있다.

하지만 오른쪽에 객체가 전혀 없을경우는 글로벌로 오버로딩을 해놨더라도 일반적인 연산이 수행된다 (당연)

 

비교연산자 오버로딩

어려운건 아니다

> < <= >= == !=

6개나 되는 오버로딩을 구현하려면 엄청나게 귀찮을 뿐..

거기에 임시객체 생성 막겠다고 상수 받는 오버로딩에 타입까지 한두개 이상 지원하려면?

답이없다

 

다만 ==와 <만 구현해두면 어찌저찌 나머지 비교는 저 두개만으로 판단할 수 있다

 

#C++20

C++20에서는 비교연산자의 오버로딩이 비교적 간단해졌다

일단 한쪽 방향만 구현해두면 자동으로 양방향 다 수행가능

==만 만들어두면 !=은 자동으로 만들어 줌

==과 <=>만 만들어두면 여섯가지 비교 연산자를 자동으로 만들어줌 !!!!!

(==는 성능문제로 따로 만들어 두는것이 좋다고 합니다)

 

내부적으로는 여섯가지 비교 연산자를  <=>로 변환해서 계산된다고 함

 

컴파일러가 생성한 비교연산자

C++20의 자동생성 기능을 이용하여 코드를 더 줄일 수 있다.

==과 <=>를 default로 선언해버리면 자동으로 각 데이터 멤버를 차례로 비교하는 함수를 만들어줌

 

심지어 <=>만 default로 선언해도 ==까지 자동을 만들어 줌

심지어 <=> 오버로딩의 리턴타입으로 auto를 지정할 수도 있다. 데이터 멤버에 대한 연산 결과를 기반으로 리턴 타입을 추론. 단 모든 멤버가 <=>를 지원하는 타입이거나 그에 준하는 오버로딩이 완료된 상태여야 함

 

디폴트로 <=>를 지정하는게 가능한 상황이면 그렇게 하는게 나음. 새로운 멤버를 추가할때마다 직접 수정할 필요가 없기 때문

중요하니 정리

 

 

9.8 안정된 인터페이스 만들기

C++은 public private 모두 한 클래스에 정의되기 때문에 추상화 원칙에 잘 맞지 않긴 함

 

하지만 인터페이스를 보다 간결하게 구성하고 구현 세부사항을 모두 숨겨서 인터페이스를 안정적으로 유지하는 방법이 있음 -> 작성할 클래스마다 인터페이스 클래스와 구현 클래스를 따로 정의하기

 

구현 클래스 <- 우리가 흔히 작성하는 클래스 코드

인터페이스 클래스 <- 구현 클래스와 똑같이 public 메서드를 제공하되 구현 클래스 객체에 대한 포인터를 갖는 데이터 멤버 하나만 정의

핌플 이디엄(핌플 구문) 또는 브릿지 패턴이라 부름

인터페이스 클래스는 단순히 구현 클래스 객체에 있는 동일한 메서드를 호출하기만함

구현코드가 변해도 인터페이스 클래스는 영향을 받지 않은다

다시 컴파일할 일이 줄어든다

# 인터페이스 클래스에 존재하는 유일한 데이터 멤버를 구현 클래스에 대한 포인터로 정의해야 효과가 있다.

 

 

~503p