<aside> <img src="/icons/list_gray.svg" alt="/icons/list_gray.svg" width="40px" />
목차
</aside>
<aside> <img src="/icons/dialogue_blue.svg" alt="/icons/dialogue_blue.svg" width="40px" />
Call Back이란?
Call (호출하다) + Back (뒤로/되돌아) = "다시 호출하다"
콜백 함수를 구현하는 여정을 떠나볼 것인데, 함수 포인터, 함수 객체, 그리고 템플릿을 활용해 유연하게 설계할 수 있다.
함수 포인터는 함수의 주소를 저장하고 가리키는 포인터이다. 함수도 메모리 공간에 코드 형태로 저장되어 있기 때문에, 이 함수의 시작 주소를 포인터로 가리킬 수 있다.
함수 포인터를 사용하면 프로그램 실행 중에 호출할 함수를 동적으로 변경할 수 있다는 장점이 있다.
반환타입 (*포인터이름)(매개변수들...);
// 예시
**int (*fp)(int, int);** // int형 반환값과 두 개의 int 매개변수를 가진 함수를 가리키는 포인터
아래 코드는 게임에서 버튼을 눌렀을 때 다른 공격 기능을 수행하도록 하는 간단한 예제이다.
#include <iostream>
// 공격 함수 정의
void SwordAttack() {
std::cout << "Sword Attack!" << std::endl;
}
void BowAttack() {
std::cout << "Bow Attack!" << std::endl;
}
// 버튼 클래스
class Button {
public:
void (*onClick)(); // 함수 포인터로 콜백 저장
void Press() {
if (onClick) {
onClick(); // 등록된 콜백 호출
}
}
};
int main() {
Button attackButton;
// Sword 공격 설정
attackButton.onClick = SwordAttack;
attackButton.Press(); // 출력: Sword Attack!
// Bow 공격 설정
attackButton.onClick = BowAttack;
attackButton.Press(); // 출력: Bow Attack!
return 0;
}
위의 예제에서 볼 수 있듯이, 함수 포인터를 사용하면 Button 클래스의 onClick 멤버를 통해 다양한 공격 함수를 동적으로 할당하고 실행할 수 있다.
하지만 함수 포인터의 선언이 복잡하고 가독성이 떨어진다는 단점이 있어, 이를 개선하기 위해 typedef를 사용할 수 있다.
typedef는 기존 타입에 새로운 별칭을 붙이는 키워드이다. 함수 포인터와 함께 사용하면 복잡한 함수 포인터 선언을 더 읽기 쉽게 만들 수 있다.
typedef void (*AttackFunction)(); // 함수 포인터 타입 정의
class Button {
public:
AttackFunction onClick; // 더 읽기 쉬워진 함수 포인터 선언
// ... 나머지 코드는 동일
};
특히 여러 곳에서 동일한 형태의 함수 포인터를 사용할 때 유용하며, 코드의 가독성과 유지보수성을 크게 향상시킬 수 있다.
또 한 나중에 함수 포인터의 타입을 변경해야 할 경우, typedef가 선언된 한 곳만 수정하면 되므로, 객체 지향적 프로그래밍에서는 매우 효율적이다.
함수 객체는 함수처럼 동작하는 객체를 말한다. 함수 객체를 생성하기 위해서는, operator() 연산자를 오버로딩하여 함수처럼 호출할 수 있는 클래스로 만든다. 함수 객체는 함수 포인터보다 더 유연하고 효율적인데, 상태를 가질 수 있고 인라인화가 가능하다는 장점이 있다.
#include <iostream>
// 함수 객체 정의
class SwordAttack {
int damage; // 상태를 저장
public:
SwordAttack(int dmg) : damage(dmg) {}
void operator()() const {
std::cout << "Sword Attack! Damage: " << damage << std::endl;
}
};
class BowAttack {
int damage; // 상태를 저장
public:
BowAttack(int dmg) : damage(dmg) {}
void operator()() const {
std::cout << "Bow Attack! Damage: " << damage << std::endl;
}
};
// 버튼 클래스
class Button {
public:
std::function<void()> onClick; // 함수 객체를 저장
void Press() {
if (onClick) {
onClick(); // 콜백 호출
}
}
};
int main() {
Button attackButton;
// Sword 공격 설정
attackButton.onClick = SwordAttack(50);
attackButton.Press(); // 출력: Sword Attack! Damage: 50
// Bow 공격 설정
attackButton.onClick = BowAttack(30);
attackButton.Press(); // 출력: Bow Attack! Damage: 30
return 0;
}
위 코드에서 각 공격 함수 객체는 damage라는 멤버 변수를 가지고 있어, 공격력을 동적으로 설정할 수 있다.
또한 std::function을 사용하여 함수 객체를 저장함으로써, 일반 함수, 멤버 함수, 람다 표현식 등 다양한 종류의 호출 가능한 객체를 저장할 수 있는 유연성을 제공한다.
템플릿은 타입에 독립적인 코드를 작성할 수 있게 해준다. 템플릿을 사용하면 다양한 데이터 타입에 대해 동일한 알고리즘이나 클래스를 재사용할 수 있어, 코드의 중복을 줄이고 유지보수성을 높일 수 있다.
특히 컴파일 시점에 타입 검사가 이루어져 타입 안정성을 보장하면서도 실행 시간에는 오버헤드가 없다는 장점이 있다.
#include <iostream>
// 템플릿 함수 예시
template <typename T>
T Max(T a, T b) {
return (a > b) ? a : b;
}
// 템플릿 클래스 예시
template <typename T>
class Container {
private:
T data;
public:
Container(T value) : data(value) {}
T getValue() { return data; }
void setValue(T value) { data = value; }
};
int main() {
// 템플릿 함수 사용
std::cout << "Max(10, 20): " << Max(10, 20) << std::endl;
std::cout << "Max(3.14, 2.72): " << Max(3.14, 2.72) << std::endl;
std::cout << "Max(\\"apple\\", \\"banana\\"): " << Max("apple", "banana") << std::endl;
// 템플릿 클래스 사용
Container<int> intContainer(42);
Container<std::string> strContainer("Hello");
std::cout << "Int container: " << intContainer.getValue() << std::endl;
std::cout << "String container: " << strContainer.getValue() << std::endl;
return 0;
}
템플릿 함수
Max 함수는 어떤 타입의 두 값이든 비교할 수 있다.
>)가 정의된 모든 타입에 사용 가능하다.템플릿 클래스
Container 클래스는 어떤 타입의 데이터든 저장하고 관리할 수 있다.
vector, list, map 등도 템플릿 클래스로 구현되어 있다.템플릿 특수화
템플릿 특수화는 특정 타입에 대해 템플릿의 동작을 다르게 정의하는 기능이다. 일반적인 템플릿과 다른 특별한 처리가 필요한 타입이 있을 때 유용하게 사용된다.
template <typename T>
void print(T value) {
std::cout << value << std::endl;
}
// char* 타입에 대한 특수화
template <>
void print<char*>(char* value) {
std::cout << "문자열: " << value << std::endl;
}
위 코드에서는 일반적인 타입에 대해서는 단순히 값을 출력하지만, char* 타입의 경우 **"문자열: "**이라는 접두사를 붙여 출력하도록 특수화했다.
출력:
int main() {
int num = 42;
char* str = "Hello";
print(num); // 출력: 42
print(str); // 출력: **문자열: Hello**
return 0;
}
그러면 이제 지금까지 배운 3종 세트 개념들을 모두 활용하여 콜백 함수를 구현해보겠다.
아래 코드는 템플릿, 함수 객체, 함수 포인터를 모두 활용하여 유연하고 재사용 가능한 버튼 클래스를 구현한 것이다.
#include <iostream>
#include <functional>
// 템플릿 기반 버튼 클래스 (범용 설계)
template <typename Callback>
class Button {
public:
Callback onClick;
void Press() {
if (onClick) {
onClick();
}
}
};
// 공격 클래스 (함수 객체)
class WeaponAttack {
public:
std::string weaponType;
int damage;
WeaponAttack(std::string type, int dmg) : weaponType(type), damage(dmg) {}
void operator()() const {
std::cout << weaponType << " Attack! Damage: " << damage << std::endl;
}
};
// 전역 공격 함수 (함수 포인터)
void MagicAttack() {
std::cout << "Magic Attack! Damage: 100" << std::endl;
}
int main() {
// 1. 함수 포인터 사용
Button<void (*)()> magicButton;
magicButton.onClick = MagicAttack;
magicButton.Press(); // 출력: Magic Attack! Damage: 100
// 2. 함수 객체 사용
Button<std::function<void()>> weaponButton;
weaponButton.onClick = WeaponAttack("Sword", 50);
weaponButton.Press(); // 출력: Sword Attack! Damage: 50
return
}
템플릿
template <typename Callback>함수 포인터