C++ 함수 오버로딩은 C++만의 것이라고 보긴 어렵다.
C에서도 함수 오버로딩과 비슷한 기능이 있다.
_Generic 키워드를 사용하여 컴파일 시간에 인수의 형식을 기반으로
식을 선택하는 코드를 작성하면 된다.
인수의 형식을 호출할 함수가 선택되는 C++의 오버로딩과 유사하다.
여기서는 인수의 형식을 기반으로 평가할 식이 선택된다.
예를 들어
_Generic(42, int : "integer", char : "character", default : "unknown");은
42의 형식을 평가하고 목록에서 일치하는 형식 int를 검색한다.
이 형식을 찾아 "integer"를 반환한다.
generic-selection: ( , )
_Generic assignment-expressionassoc-list
assoc-list: ,
association
assoc-listassociation
association:
type-name: :assignment-expression
defaultassignment-expression
첫 번째 assignment-exptession을 제어 식이라고 한다.
제어 식의 형식은 컴파일 시간에 결정되고
assoc-list와 비교되어 평가 및 반환할 식을 찾는다.
제어 식은 평가되지 않는다.
예를 들어 _Generic(intFunc(), int : "integer", default : "error");은
런타임에 intFunc을 호출하지 않는다.
제어 식의 형식이 결정되면 const, valatile 및
restrict은 assoc-list와 비교하기 전에 제거된다.
이게 무슨 말이냐면
C에서 _Generic 키워드를 사용하여
함수 오버로딩과 유사한 기능을 구현하는 방법을 말하는 것이다.
1. _Generic 키워드를 사용하면 컴파일 시점에 변수나 표현식의 타입을 검사한다.
2 .해당 타입에 맞는 코드를 선택할 수 있다는 것이다.
이렇게 하면 C++의 함수 오버로딩과 비슷한 효과를 낼 수 있다는 것이다.
1. _Generic 키워드의 기본 사용법
_Generic을 사용하면 컴파일 시간에 인수의 타입을 평가하고,
그 타입에 따라 적절한 표현식을 선택할 수 있다.
_Generic(42, int : "integer", char : "character", default : "unknown");
2. 형식 지정과 선택의 동작 방식
_Generic의 구문은 다음과 같다.
_Generic(제어 식, 형식 : 결과, 형식 : 결과, ... , default : 기본값);
(제어 식은 타입을 결정하는 역할이다.)
그러면 assoc-list에 해당하는 형식 들은 뭘까? C++ 에서 매개변수와 비슷하다.
그럼 _Generic의 사용 제약 조건에 어떤 것이 들어갈까?
제약조건
- assoc-list는 동일한 형식을 두 번 이상 지정할 수 없다.
- assoc-list는 열거형의 기본 형식과 같이 서로 호환되는 형식을 지정할 수 없다.
- 일반 선택에 기본값이 없는 경우 제어 식에는 일반 연결 목록에
호환되는 형식 이름이 하나만 있어야 한다.
이렇게 말하면 잘 이해가 되지 않는다. 따라서 추가적으로 코드를 통한 비교를 해보자.
1. 같은 타입을 두 번 이상 지정할 수 없음
// 잘못된 예시: int를 두 번 지정
_Generic(42, int : "정수", int : "또 다른 정수", default : "기타");
// 컴파일 오류 발생
올바른 예시
_Generic(42, int : "정수", default : "기타"); // 올바른 사용
2. 기본값(default)이 없는 경우, 타입이 하나만 일치해야 함
잘못된 예시
int x = 5;
// 잘못된 예시: 기본값이 없고 일치하는 타입이 없음
_Generic(x, double : "실수"); // 컴파일 오류 발생
// 잘못된 예시: 기본값이 없고 일치하는 타입이 여러 개
_Generic(x, int : "정수", int : "또 다른 정수"); // 컴파일 오류 발생
올바른 예시
_Generic(x, int : "정수", default : "기타"); // 기본값 추가
_Generic(x, int : "정수"); // 일치하는 타입 한 개
3. 호환되는 형식을 지정할 수 없음 (enum 예시 경우)
assoc-list 에 호환되는 타입을 함께 지정할 수 없다.
예를 들어 열거형 타입과 **열거형의 기본 타입 (ex.int)&**를
함께 지정하는 것은 불가능하다.
열거형은 내부적으로 정수형을 사용하더라도 다른 타입으로 간주되기 때문이다.
enum Color { RED, GREEN, BLUE };
enum Color color = RED;
// 잘못된 예시: enum Color와 int는 호환되므로 함께 지정 불가
_Generic(color, enum Color : "열거형", int : "정수", default : "기타");
// 컴파일 오류 발생
올바른 예시에는 열거형 타입을 하나만 지정해야 한다.
_Generic(color, enum Color : "열거형", default : "기타"); // 올바른 사용
제약 조건 | 설명 |
같은 타입을 두 번 이상 지정할 수 없음 | assoc-list 에서 같은 타입을 중복해서 지정하면 안된다. |
호환 되는 타입을 함께 지정할 수 없음 | 열거형과 그 기본 타입 (int)처럼 호환되는 타입은 함께 지정할 수 없다. |
기본 값이 없는 경우, 타입이 하나만 일치해야 함 | default가 없으면 제어식과 일치하는 타입이 정확히 하나만 있어야 한다. |
올바른 _Generic 예시
// Compile with /std:c11
#include <stdio.h>
/* Get a type name string for the argument x */
#define TYPE_NAME(X) _Generic((X), \
int: "int", \
char: "char", \
double: "double", \
default: "unknown")
int main()
{
printf("Type name: %s\n", TYPE_NAME(42.42));
// The following would result in a compile error because
// 42.4 is a double, doesn't match anything in the list,
// and there is no default.
// _Generic(42.4, int: "integer", char: "character"));
}
/* Output:
Type name: double
*/
그럼 _Generic을 공부했으니
C++ 함수 오버로딩과 C _Generic의 차이를 요약해보자.
특징 | C++ 함수 오버로딩 | C _Generic |
사용 방식 | 함수 이름을 동일하게 하여 타입에 따라 호출을 자동 선택 해준다. | _Generic 키워드와 매크로를 사용해준다. |
타입 검사 시점 | 컴파일 시점에서 타입을 확인하고 적절한 함수를 선택한다. | 컴파일 시점에 제어식의 타입을 확인한다. |
다형성 구현 방식 | 함수 오버로딩 직접 지원 | 매크로와 _Generic으로 간접 구현 |
가독성과 유연성 | 코드가 간결하고 가독성이 높음 | 매크로와 _Generic으로 다소 복잡함 |
타입 안전성 | 타입 안전성 보장 | 타입 안전성이 떨어질 수 있다. |
결론
C++에서 함수 오버로딩을 사용하여 더 가독성 높은 코드를 작성할 수 있지만,
C에서도 _Generic을 활용해 타입 기반 선택을 구현할 수 있다.
다만 _Generic은 제한적이기 때문에 C에서 함수 오버로딩과 비슷한 구현을 하려면
매크로를 사용해야 하며, 사용 시 코드가 복합하다는 것일 뿐이지 비슷하다.
// C
#include <stdio.h>
#define TYPE_NAME(x) _Generic((x), \
int: "정수형", \
double: "실수형", \
char*: "문자열형", \
default: "알 수 없는 형식" \
)
int main() {
int a = 10;
double b = 3.14;
char* c = "Hello";
printf("a는 %s입니다.\n", TYPE_NAME(a)); // 제어식: a, int 타입
printf("b는 %s입니다.\n", TYPE_NAME(b)); // 제어식: b, double 타입
printf("c는 %s입니다.\n", TYPE_NAME(c)); // 제어식: c, char* 타입
return 0;
}
// C++
#include <iostream>
using namespace std;
void print(int x) {
cout << "정수: " << x << endl;
}
void print(double x) {
cout << "실수: " << x << endl;
}
int main() {
print(10); // int 타입 -> print(int) 호출
print(3.14); // double 타입 -> print(double) 호출
return 0;
}
결정적 차이는
C++에서는 인수 타입이 함수 선택의 기준이며,
함수 오버로딩이 이를 자동으로 처리한다.
C에서는 제어식이 타입을 결정하는 역할을 하며,
_Generic을 통해 해당 타입에 맞는
표현식을 선택하는 방식으로 오버로딩 비슷한 효과를 낸다.
둘 다 공통점은 컴파일 타임에 바인딩 된다는 것이다.
그럼 이제 C++의 함수의 오버로딩을 복습한다는 의미로 간략히 알아보자.
메서드 오버로딩
C에서는 메서드 오버로딩이 없다
-> 멤버 함수라는 것이 없다.
멤버 함수라는 것이 없을 뿐이지 일반적인 오버로딩은 있다.(_Generic)
Java와 C++은 있다.
매개 변수가 다르면 호출 할 때
전달해 주는 인자에 따라 어떤 것이 호출할 지 결정된다.
함수 오버로딩이란?
매개변수 목록을 제외하고 모든 게 동일하지만
반환형은 상관이 없다.
어떤 것이 중복되지 않는 함수 시그니처일까?
void Print(int score); // (1)
void Print(const char* name); // (2)
void Print(float gpa, const char* name); // (3)
int Print(int score); // (4)
int Print(float gpa); // (5)
4번이다.
이미 매개변수 int형 1개 있는 함수는
이미 있는데 왜 또 이래... 싶은 것이다.
그래서 컴파일 에러가 발생한다 (컴파일 타임 바인딩되는 것이니까)
오버로딩 매칭하기
오버로딩 매칭이란?
오버로딩 된 함수 중에 어떤 함수를 호출해야 하는지 판단하는 과정이다.
함수 매칭 결과는 3개가 있다.
- 매칭되는 함수는 찾을 수 없음 //컴파일 에러
- 매칭되는 함수를 여러 개 찾음
- 누굴 호출할지 모호 -> 컴파일 에러
- 가장 적합한 함수 하나를 찾음 -> ok
'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글
연산자 오버로딩을 남용하는 것 (1) | 2024.11.10 |
---|---|
연산자 오버로딩과 const (0) | 2024.11.06 |
복사 생성자 (4) | 2024.11.02 |
구조체와 클래스의 차이 (1) | 2024.11.02 |
new와 malloc의 차이 (1) | 2024.11.01 |