[2026] C++ std::any vs void* Complete Comparison | Type-Safe vs Unsafe Type Erasure

[2026] C++ std::any vs void* Complete Comparison | Type-Safe vs Unsafe Type Erasure

이 글의 핵심

Master C++ type erasure: std::any (type-safe, runtime checks) vs void* (unsafe, manual casting). Complete comparison with use cases, performance, and when to choose each.

Overview

Both std::any and void* provide type erasure—storing values of any type—but with vastly different safety guarantees. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// std::any (C++17, type-safe)
std::any value = 42;
int x = std::any_cast<int>(value);  // Runtime type check
// void* (C, unsafe)
void* ptr = new int(42);
int y = *(int*)ptr;  // No type check, dangerous!

Key difference: std::any remembers the stored type and checks it at runtime; void* forgets the type entirely.

std::any

Basic Usage

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <any>
#include <iostream>
int main() {
    std::any value;
    
    // Store int
    value = 42;
    cout << std::any_cast<int>(value) << endl;  // 42
    
    // Store string
    value = std::string("hello");
    cout << std::any_cast<std::string>(value) << endl;  // hello
    
    // Store custom type
    struct Point { int x, y; };
    value = Point{10, 20};
    auto p = std::any_cast<Point>(value);
    cout << p.x << ", " << p.y << endl;  // 10, 20
}

Output:

42
hello
10, 20

Type Checking

다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

std::any value = 42;
// Check type
if (value.type() == typeid(int)) {
    cout << "It's an int" << endl;
}
// has_value()
if (value.has_value()) {
    cout << "Has value" << endl;
}
// Wrong type cast → exception
try {
    auto s = std::any_cast<std::string>(value);  // Throws!
} catch (const std::bad_any_cast& e) {
    cerr << "Bad cast: " << e.what() << endl;
}

Output:

It's an int
Has value
Bad cast: bad any_cast

Safe Casting

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

std::any value = 42;
// Pointer cast (returns nullptr on failure)
int* ptr = std::any_cast<int>(&value);
if (ptr) {
    cout << *ptr << endl;  // 42
}
// Wrong type → nullptr
std::string* strPtr = std::any_cast<std::string>(&value);
if (!strPtr) {
    cout << "Not a string" << endl;
}

Output:

42
Not a string

void*

Basic Usage

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

void* ptr = new int(42);
// Cast back to int*
int* intPtr = (int*)ptr;
cout << *intPtr << endl;  // 42
delete intPtr;

Warning: No type safety—you must remember the original type.

Dangerous Scenarios

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Wrong type cast (undefined behavior)
void* ptr = new int(42);
double* dblPtr = (double*)ptr;  // Wrong type!
cout << *dblPtr << endl;  // Garbage or crash
// ❌ Forgot to delete (memory leak)
void* ptr = new int(42);
// ....ptr goes out of scope, never deleted
// ❌ Double delete
void* ptr = new int(42);
delete (int*)ptr;
delete (int*)ptr;  // Crash!

C API Example

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C API using void*
void processData(void* userData, void (*callback)(void*)) {
    // ....processing ...
    callback(userData);
}
// Usage
struct Context {
    int id;
    std::string name;
};
void myCallback(void* ptr) {
    Context* ctx = (Context*)ptr;  // Manual cast
    cout << ctx->id << ", " << ctx->name << endl;
}
int main() {
    Context ctx{1, "Alice"};
    processData(&ctx, myCallback);
}

Output:

1, Alice

Comparison

Feature Comparison

Featurestd::anyvoid*
Type safety✅ Runtime checks❌ No checks
Exception on wrong cast✅ Yes❌ No (UB)
Type info stored✅ Yes❌ No
Memory management✅ Automatic❌ Manual
Small object optimization✅ Yes❌ No
C compatibility❌ No✅ Yes
PerformanceGoodSlightly faster

Safety Comparison

아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// std::any: Safe
std::any value = 42;
try {
    auto s = std::any_cast<std::string>(value);  // Throws bad_any_cast
} catch (const std::bad_any_cast&) {
    cout << "Caught wrong type" << endl;
}
// void*: Unsafe
void* ptr = new int(42);
std::string* s = (std::string*)ptr;  // Compiles, but UB!
cout << *s << endl;  // Crash or garbage

Performance Comparison

// Benchmark: 1,000,000 operations
// std::any: 15ms (type info overhead)
// void*: 10ms (no overhead)

Key: void* is slightly faster, but the difference is negligible in most applications.

When to Use Each

Use std::any When:

  • ✅ Type safety is important
  • ✅ Storing heterogeneous data in containers
  • ✅ Building modern C++ APIs
  • ✅ Runtime type checks needed 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Event system
struct Event {
    std::string type;
    std::any data;
};
void handleEvent(const Event& e) {
    if (e.type == "click") {
        auto pos = std::any_cast<Point>(e.data);
        // ...
    }
}

Use void* When:

  • ✅ Interfacing with C APIs
  • ✅ Legacy code maintenance
  • ✅ Extreme performance requirements
  • ✅ Custom memory management 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// C API callback
void setCallback(void* userData, void (*callback)(void*)) {
    // ...
}

Common Mistakes

Mistake 1: Forgetting Type with void*

다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Lost type information
void* ptr = new std::string("hello");
// ....later ...
int* intPtr = (int*)ptr;  // Wrong type! UB

Fix: Use std::any or document type carefully.

Mistake 2: Memory Leak with void*

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Memory leak
void* ptr = new int(42);
// ....forgot to delete
// ✅ Use smart pointer
std::unique_ptr<void, void(*)(void*)> ptr(
    new int(42),
    [](void* p) { delete (int*)p; }
);

Fix: Use RAII or std::any.

Mistake 3: Wrong any_cast

아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Value cast throws on failure
std::any value = 42;
auto s = std::any_cast<std::string>(value);  // Throws!
// ✅ Pointer cast returns nullptr on failure
std::string* s = std::any_cast<std::string>(&value);
if (!s) {
    cout << "Not a string" << endl;
}

Practical Examples

Example 1: Heterogeneous Container

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// std::any
vector<std::any> data;
data.push_back(42);
data.push_back(std::string("hello"));
data.push_back(3.14);
for (auto& item : data) {
    if (item.type() == typeid(int)) {
        cout << "int: " << std::any_cast<int>(item) << endl;
    } else if (item.type() == typeid(std::string)) {
        cout << "string: " << std::any_cast<std::string>(item) << endl;
    } else if (item.type() == typeid(double)) {
        cout << "double: " << std::any_cast<double>(item) << endl;
    }
}

Output:

int: 42
string: hello
double: 3.14

Example 2: Plugin System

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Plugin interface
class Plugin {
public:
    virtual ~Plugin() = default;
    virtual void execute(std::any context) = 0;
};
class LogPlugin : public Plugin {
public:
    void execute(std::any context) override {
        if (context.type() == typeid(std::string)) {
            auto msg = std::any_cast<std::string>(context);
            cout << "Log: " << msg << endl;
        }
    }
};
int main() {
    LogPlugin plugin;
    plugin.execute(std::string("Hello"));
}

Output:

Log: Hello

Example 3: C API Wrapper

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C API
extern "C" {
    void c_callback(void* userData);
}
// C++ wrapper
class CallbackWrapper {
    std::function<void()> func;
    
public:
    CallbackWrapper(std::function<void()> f) : func(f) {}
    
    static void invoke(void* ptr) {
        auto* wrapper = (CallbackWrapper*)ptr;
        wrapper->func();
    }
    
    void* getUserData() {
        return this;
    }
};
void registerCallback() {
    CallbackWrapper wrapper([]() {
        cout << "Callback invoked" << endl;
    });
    
    c_callback(wrapper.getUserData());
}

Production Patterns

Pattern 1: Event System with std::any

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class EventBus {
    unordered_map<string, vector<function<void(std::any)>>> handlers;
    
public:
    void subscribe(const string& eventType, function<void(std::any)> handler) {
        handlers[eventType].push_back(handler);
    }
    
    void publish(const string& eventType, std::any data) {
        for (auto& handler : handlers[eventType]) {
            handler(data);
        }
    }
};
struct ClickEvent { int x, y; };
int main() {
    EventBus bus;
    
    bus.subscribe("click", [](std::any data) {
        auto evt = std::any_cast<ClickEvent>(data);
        cout << "Click at " << evt.x << ", " << evt.y << endl;
    });
    
    bus.publish("click", ClickEvent{100, 200});
}

Output:

Click at 100, 200

Pattern 2: Config System

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Config {
    unordered_map<string, std::any> values;
    
public:
    template<typename T>
    void set(const string& key, const T& value) {
        values[key] = value;
    }
    
    template<typename T>
    T get(const string& key, const T& defaultValue = T{}) {
        auto it = values.find(key);
        if (it == values.end()) {
            return defaultValue;
        }
        
        try {
            return std::any_cast<T>(it->second);
        } catch (const std::bad_any_cast&) {
            return defaultValue;
        }
    }
};
int main() {
    Config cfg;
    cfg.set("port", 8080);
    cfg.set("host", std::string("localhost"));
    cfg.set("debug", true);
    
    cout << cfg.get<int>("port") << endl;        // 8080
    cout << cfg.get<string>("host") << endl;     // localhost
    cout << cfg.get<bool>("debug") << endl;      // 1
    cout << cfg.get<int>("missing", 9000) << endl;  // 9000 (default)
}

Output: 다음은 간단한 code 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

8080
localhost
1
9000

Summary

Key Points

  1. std::any: Type-safe, runtime checks, exceptions
  2. void*: Unsafe, manual casting, no checks
  3. Use std::any: For modern C++ code
  4. Use void*: For C API interop, legacy code
  5. Performance: void* slightly faster, but std::any overhead is minimal
  6. Safety: std::any prevents undefined behavior

Decision Matrix

ScenarioRecommendation
Modern C++ APIstd::any
C API interopvoid*
Type safety criticalstd::any
Legacy codebasevoid* (migrate to std::any)
Extreme performancevoid* (measure first)
Heterogeneous containersstd::any or std::variant

Migration Path

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Old (void*)
void* data = new int(42);
int value = *(int*)data;
delete (int*)data;
// New (std::any)
std::any data = 42;
int value = std::any_cast<int>(data);
// Automatic cleanup

Keywords

C++ std::any, void pointer, type erasure, type safety, runtime type checking, std::any_cast, bad_any_cast One-line summary: std::any provides type-safe type erasure with runtime checks, while void* offers unsafe manual casting—prefer std::any for modern C++ code.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3