C++ using vs typedef | 타입 별칭 빠른 비교
이 글의 핵심
typedef와 using은 같은 목적(타입 별칭)이지만 문법과 템플릿 별칭 지원이 다릅니다. 이 글은 한눈에 비교하는 데 집중합니다.
이 글과 형제 글의 역할
- 이 글 (
cpp-using-typedef): 빠른 비교—표, 짧은 코드, 언제 무엇을 쓰는지 요약. - 심화 글 (C++ typedef vs using | 타입 별칭 심화 가이드): 함수·멤버 함수 포인터, 템플릿 별칭, 가독성·실무 패턴을 더 길게 다룹니다.
같은 주제를 두 번 읽는 대신, 필요에 따라 위 둘 중 하나만 골라도 됩니다.
타입 별칭이란?
타입 별칭 (Type Alias) 은 기존 타입에 새 이름을 부여하는 기능입니다. C++03의 typedef와 C++11의 using이 있습니다.
// typedef (C++03)
typedef unsigned long ulong;
typedef std::vector<int> IntVec;
// using (C++11)
using ulong = unsigned long;
using IntVec = std::vector<int>;
왜 필요한가?:
- 가독성: 복잡한 타입을 간결하게
- 유지보수: 타입 변경 시 한 곳만 수정
- 추상화: 구현 세부사항 숨김
- 이식성: 플랫폼별 타입 통일
// ❌ 복잡한 타입: 읽기 어려움
std::map<std::string, std::vector<std::pair<int, double>>> data;
// ✅ 타입 별칭: 간결
using DataMap = std::map<std::string, std::vector<std::pair<int, double>>>;
DataMap data;
typedef vs using 비교:
| 특징 | typedef | using |
|---|---|---|
| 문법 | C 스타일 | 현대적 |
| 가독성 | 낮음 | 높음 |
| 템플릿 별칭 | ❌ 불가 | ✅ 가능 |
| 함수 포인터 | 복잡 | 간결 |
| 권장 | ❌ 레거시 | ✅ C++11+ |
// typedef: 복잡
typedef void (*FuncPtr)(int, double);
// using: 간결
using FuncPtr = void(*)(int, double);
// typedef: 템플릿 별칭 불가
template<typename T>
typedef std::vector<T> Vec; // 에러!
// using: 템플릿 별칭 가능
template<typename T>
using Vec = std::vector<T>;
typedef 기본
// 기본 타입
typedef int Integer;
typedef double Real;
Integer x = 10;
Real y = 3.14;
// 포인터
typedef int* IntPtr;
IntPtr ptr = &x;
// 배열
typedef int IntArray[10];
IntArray arr = {1, 2, 3};
// 함수 포인터
typedef int (*FuncPtr)(int, int);
FuncPtr func = add;
using 기본
// 기본 타입
using Integer = int;
using Real = double;
// 포인터
using IntPtr = int*;
// 배열
using IntArray = int[10];
// 함수 포인터
using FuncPtr = int(*)(int, int);
using vs typedef
// typedef: 읽기 어려움
typedef void (*FuncPtr)(int, double);
// using: 읽기 쉬움
using FuncPtr = void(*)(int, double);
// typedef: 템플릿 별칭 불가
template<typename T>
typedef std::vector<T> Vec; // 에러!
// using: 템플릿 별칭 가능
template<typename T>
using Vec = std::vector<T>;
실전 예시
예시 1: 컨테이너 별칭
#include <vector>
#include <map>
#include <string>
// 자주 사용하는 타입
using StringVec = std::vector<std::string>;
using IntMap = std::map<std::string, int>;
using Matrix = std::vector<std::vector<int>>;
int main() {
StringVec names = {"Alice", "Bob", "Charlie"};
IntMap ages = {
{"Alice", 30},
{"Bob", 25}
};
Matrix grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
}
예시 2: 함수 포인터
// typedef 방식
typedef int (*Operation)(int, int);
// using 방식 (더 명확)
using Operation = int(*)(int, int);
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int calculate(int a, int b, Operation op) {
return op(a, b);
}
int main() {
cout << calculate(10, 5, add) << endl; // 15
cout << calculate(10, 5, multiply) << endl; // 50
}
예시 3: 템플릿 별칭
// 스마트 포인터 별칭
template<typename T>
using UniquePtr = std::unique_ptr<T>;
template<typename T>
using SharedPtr = std::shared_ptr<T>;
// 컨테이너 별칭
template<typename T>
using Vec = std::vector<T>;
template<typename K, typename V>
using Map = std::map<K, V>;
int main() {
UniquePtr<int> ptr = std::make_unique<int>(42);
Vec<int> numbers = {1, 2, 3, 4, 5};
Map<std::string, int> ages = {{"Alice", 30}};
}
예시 4: 복잡한 타입 간소화
#include <functional>
#include <vector>
// 복잡한 타입
using Callback = std::function<void(int)>;
using CallbackList = std::vector<Callback>;
class EventManager {
private:
CallbackList callbacks;
public:
void subscribe(Callback cb) {
callbacks.push_back(cb);
}
void notify(int value) {
for (auto& cb : callbacks) {
cb(value);
}
}
};
int main() {
EventManager manager;
manager.subscribe([](int x) {
std::cout << "Callback 1: " << x << '\n';
});
manager.subscribe([](int x) {
std::cout << "Callback 2: " << x * 2 << '\n';
});
manager.notify(10);
}
멤버 타입 별칭
template<typename T>
class Container {
public:
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using size_type = std::size_t;
private:
std::vector<value_type> data;
public:
void push_back(const_reference value) {
data.push_back(value);
}
reference operator[](size_type index) {
return data[index];
}
size_type size() const {
return data.size();
}
};
int main() {
Container<int> cont;
cont.push_back(10);
Container<int>::value_type x = cont[0];
cout << x << endl;
}
네임스페이스 별칭
namespace very_long_namespace_name {
class MyClass {
public:
void doSomething() {
cout << "Doing something" << endl;
}
};
}
// 네임스페이스 별칭
namespace vln = very_long_namespace_name;
int main() {
vln::MyClass obj;
obj.doSomething();
}
자주 발생하는 문제
문제 1: typedef 순서
// ❌ 읽기 어려움
typedef int* (*FuncPtr)(int);
// ✅ using이 더 명확
using FuncPtr = int*(*)(int);
문제 2: 템플릿 별칭
// ❌ typedef는 템플릿 별칭 불가
template<typename T>
typedef std::vector<T> Vec; // 에러
// ✅ using 사용
template<typename T>
using Vec = std::vector<T>;
Vec<int> numbers = {1, 2, 3};
문제 3: 타입 은닉
// ❌ 타입이 불명확
using IntVec = std::vector<int>;
IntVec vec; // 실제 타입이 뭐지?
// ✅ 명확한 경우에만 사용
// 자주 사용하거나 복잡한 타입
사용 시기
// ✅ 사용 권장
// 1. 복잡한 타입
using ComplexType = std::map<std::string, std::vector<int>>;
// 2. 자주 사용하는 타입
using StringVec = std::vector<std::string>;
// 3. 템플릿 별칭
template<typename T>
using Ptr = std::unique_ptr<T>;
// 4. 플랫폼 의존 타입
#ifdef _WIN32
using FileHandle = HANDLE;
#else
using FileHandle = int;
#endif
// ❌ 사용 지양
// 1. 간단한 타입
// using Int = int; // 불필요
// 2. 표준 타입 재정의
// using string = std::string; // 혼란
typedef vs using 비교
// typedef
typedef std::vector<int> IntVec;
typedef int (*FuncPtr)(int);
// using (권장)
using IntVec = std::vector<int>;
using FuncPtr = int(*)(int);
// 장점:
// - 더 읽기 쉬움
// - 템플릿 별칭 지원
// - 일관된 문법
실무 패턴
패턴 1: 플랫폼 추상화
// platform.h
#ifdef _WIN32
using FileHandle = HANDLE;
using SocketHandle = SOCKET;
#elif defined(__linux__)
using FileHandle = int;
using SocketHandle = int;
#elif defined(__APPLE__)
using FileHandle = int;
using SocketHandle = int;
#endif
// 사용
class File {
FileHandle handle_;
public:
File(const std::string& path);
~File();
};
패턴 2: STL 컨테이너 커스터마이징
#include <vector>
#include <memory>
// 커스텀 allocator
template<typename T>
using PoolVector = std::vector<T, PoolAllocator<T>>;
// 스마트 포인터 컨테이너
template<typename T>
using PtrVector = std::vector<std::unique_ptr<T>>;
// 사용
PoolVector<int> poolVec;
PtrVector<Widget> widgets;
widgets.push_back(std::make_unique<Widget>());
패턴 3: 콜백 타입
#include <functional>
#include <map>
#include <string>
// 콜백 타입 정의
using ErrorCallback = std::function<void(const std::string&)>;
using SuccessCallback = std::function<void(int)>;
using ProgressCallback = std::function<void(double)>;
class AsyncTask {
ErrorCallback onError_;
SuccessCallback onSuccess_;
ProgressCallback onProgress_;
public:
void setErrorCallback(ErrorCallback cb) {
onError_ = cb;
}
void setSuccessCallback(SuccessCallback cb) {
onSuccess_ = cb;
}
void execute() {
// 작업 수행
if (/* 에러 */) {
if (onError_) onError_("에러 발생");
} else {
if (onSuccess_) onSuccess_(42);
}
}
};
함수 포인터 별칭 (요약)
복잡한 시그니처를 한 줄 이름으로 묶을 때 using이 읽기 쉽습니다.
typedef void (*OldStyle)(int, double);
using NewStyle = void(*)(int, double); // “이름 = 타입”
멤버 함수 포인터, noexcept 지정, 다양한 호출 규칙까지 섞이면 typedef만으로는 실수하기 쉽습니다. 상세·패턴은 심화 가이드의 “함수 포인터 타입 별칭” 절을 참고하세요.
템플릿 별칭 (요약)
템플릿 매개변수를 받는 별칭은 using만 가능합니다. typedef로는 동일한 표현을 할 수 없습니다.
template<typename T>
using Vec = std::vector<T>;
C++11 이전의 template<typename T> struct Vec { typedef std::vector<T> type; }; 같은 우회는 가능하지만 장황하므로, 모던 코드에서는 using이 표준입니다.
가독성·실무에서의 선택
| 상황 | 권장 |
|---|---|
| 새 C++ 코드 | using |
| C 공유 헤더·레거시 규칙 | typedef 유지 가능 |
| 템플릿 별칭 | using 필수 |
| 복잡한 함수 포인터 | using (의도가 오른쪽 타입에 집중됨) |
팀 컨벤션에 “항상 using”이 있으면 그에 따르는 것이 리뷰 비용을 줄입니다.
FAQ
Q1: using vs typedef?
A:
using: 권장 (C++11 이상), 가독성 높음typedef: 레거시 코드
// typedef: 복잡
typedef void (*FuncPtr)(int);
// using: 간결
using FuncPtr = void(*)(int);
Q2: 언제 타입 별칭을 사용하나요?
A:
- 복잡한 타입: 가독성 향상
- 자주 사용하는 타입: 유지보수 용이
- 템플릿 별칭: 재사용
// 복잡한 타입
using DataMap = std::map<std::string, std::vector<int>>;
// 자주 사용
using StringVec = std::vector<std::string>;
// 템플릿 별칭
template<typename T>
using Ptr = std::unique_ptr<T>;
Q3: 성능 차이는?
A: 없습니다. 타입 별칭은 컴파일 타임에만 영향을 줍니다.
// 동일한 코드 생성
std::vector<int> v1;
using IntVec = std::vector<int>;
IntVec v2;
Q4: 템플릿 별칭은?
A: using만 가능합니다. typedef는 불가능합니다.
// ❌ typedef: 불가능
template<typename T>
typedef std::vector<T> Vec;
// ✅ using: 가능
template<typename T>
using Vec = std::vector<T>;
Q5: 네임스페이스 별칭은?
A: namespace alias = original; 을 사용합니다.
namespace very_long_name {
class MyClass {};
}
namespace vln = very_long_name;
vln::MyClass obj;
Q6: 타입 별칭과 새 타입의 차이는?
A:
- 타입 별칭: 기존 타입의 다른 이름 (동일한 타입)
- 새 타입:
enum class,struct(다른 타입)
// 타입 별칭: 동일한 타입
using Int = int;
Int x = 10;
int y = x; // OK
// 새 타입: 다른 타입
enum class StrongInt : int {};
StrongInt a = StrongInt{10};
// int b = a; // 에러: 다른 타입
Q7: 멤버 타입 별칭은?
A: 클래스 내부에서 using 을 사용합니다.
template<typename T>
class Container {
public:
using value_type = T;
using reference = T&;
using const_reference = const T&;
};
Container<int>::value_type x = 10;
Q8: 타입 별칭 학습 리소스는?
A:
- “Effective Modern C++” by Scott Meyers (Item 9)
- cppreference.com - Type alias
- “C++ Primer” by Stanley Lippman
관련 글: typedef, template, auto.
한 줄 요약: 타입 별칭은 기존 타입에 새 이름을 부여하여 가독성과 유지보수성을 높이는 기능입니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ typedef vs using | 타입 별칭 심화 가이드
- C++ namespace | “이름 충돌 방지” 완벽 가이드
- C++ 템플릿 템플릿 인자 | template template parameter 가이드
관련 글
- C++ typedef vs using | 타입 별칭 심화 가이드
- C++ 이름 은닉 |
- C++ namespace |
- C++ 클래스 템플릿 | 제네릭 컨테이너와 부분 특수화
- 배열과 리스트 | 코딩 테스트 필수 자료구조 완벽 정리