C++ using vs typedef | 타입 별칭 빠른 비교

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 비교:

특징typedefusing
문법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:

관련 글: typedef, template, auto.

한 줄 요약: 타입 별칭은 기존 타입에 새 이름을 부여하여 가독성과 유지보수성을 높이는 기능입니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ typedef vs using | 타입 별칭 심화 가이드
  • C++ namespace | “이름 충돌 방지” 완벽 가이드
  • C++ 템플릿 템플릿 인자 | template template parameter 가이드

관련 글

  • C++ typedef vs using | 타입 별칭 심화 가이드
  • C++ 이름 은닉 |
  • C++ namespace |
  • C++ 클래스 템플릿 | 제네릭 컨테이너와 부분 특수화
  • 배열과 리스트 | 코딩 테스트 필수 자료구조 완벽 정리