C++ 초급 - 12. 최신 C++ 기능 소개 (1 - C++11: 주요 기능 소개)
2025. 2. 22. 18:02ㆍ프로그래밍 언어/C++
📌 12. 최신 C++ 기능 소개
C++은 지속적인 발전을 거듭하며 새로운 기능이 추가되어 코드의 가독성을 높이고 성능을 최적화하는 기능들을 제공한다.
초급 과정에서는 각 버전별 핵심 기능을 간단히 이해하는 데 초점을 맞춘다.
📌 12.1 C++11: 주요 기능 소개
C++11은 C++의 가장 큰 변화 중 하나로, 타입 추론, 메모리 관리, 이동 시멘틱스 등 코드의 안전성과 효율성을 향상시키는 기능이 추가되었다.
📌 1. auto와 decltype을 활용한 타입 추론
🔹 (1) auto를 활용한 타입 자동 유추
- 변수의 타입을 컴파일러가 자동으로 추론.
- 타입을 명확히 알 필요가 없을 때 유용.
💡 예제: auto 사용법
#include <iostream>
#include <vector>
int main() {
auto num = 10; // int
auto pi = 3.14; // double
auto isValid = true; // bool
std::vector<int> v = {1, 2, 3};
auto it = v.begin(); // std::vector<int>::iterator
std::cout << "num: " << num << ", pi: " << pi << ", isValid: " << isValid << std::endl;
return 0;
}
🔹 출력 결과
num: 10, pi: 3.14, isValid: 1
💡 설명
- auto를 사용하면 변수 선언 시 타입을 직접 지정하지 않아도 됨.
- 컨테이너의 반복자(std::vector<int>::iterator)도 auto를 사용하면 더 간결하게 표현 가능.
🔹 (2) decltype을 활용한 타입 추론
- decltype은 주어진 표현식에서 타입을 추론하여 반환.
- 타입을 기반으로 변수를 선언할 때 사용.
💡 예제: decltype 사용법
#include <iostream>
int main() {
int x = 42;
decltype(x) y = 10; // y의 타입은 x와 동일 (int)
std::cout << "x: " << x << ", y: " << y << std::endl;
return 0;
}
🔹 출력 결과
x: 42, y: 10
💡 설명
- decltype(x)는 x의 타입(int)을 추론하여 y를 선언.
📌 2. nullptr 도입으로 NULL보다 안전한 포인터 처리
🔹 (1) 기존 NULL의 문제점
- C++에서는 NULL이 0으로 정의되어 있어, 정수와 혼동될 수 있음.
- nullptr을 도입하여 포인터를 명확하게 표현 가능.
💡 예제: nullptr 사용법
#include <iostream>
void func(int x) {
std::cout << "정수 함수 호출: " << x << std::endl;
}
void func(char* p) {
std::cout << "포인터 함수 호출" << std::endl;
}
int main() {
func(NULL); // 정수 함수 호출 (NULL이 0으로 해석됨)
func(nullptr); // 포인터 함수 호출 (nullptr 사용)
return 0;
}
🔹 출력 결과
정수 함수 호출: 0
포인터 함수 호출
💡 설명
- NULL을 전달하면 정수(int)와 포인터(char*) 함수 중 어떤 것이 호출될지 애매.
- nullptr을 사용하면 포인터를 명확하게 구분할 수 있음.
📌 3. 스마트 포인터 (std::unique_ptr, std::shared_ptr)
🔹 (1) 기존 new / delete의 문제점
- 수동으로 메모리를 할당(new)하고 해제(delete)해야 함.
- 예외가 발생하면 delete를 호출하지 못해 메모리 누수가 발생할 수 있음.
🔹 (2) std::unique_ptr (소유권이 하나인 스마트 포인터)
- 하나의 포인터만 객체를 소유하며, 복사 불가능.
- std::move()를 사용하여 소유권 이전 가능.
💡 예제: std::unique_ptr 사용법
#include <iostream>
#include <memory> // 스마트 포인터 사용을 위한 헤더
int main() {
std::unique_ptr<int> ptr = std::make_unique<int>(42);
std::cout << "값: " << *ptr << std::endl; // 42
// std::move()를 사용하여 소유권 이전
std::unique_ptr<int> ptr2 = std::move(ptr);
std::cout << "이전된 값: " << *ptr2 << std::endl;
return 0;
}
🔹 출력 결과
값: 42
이전된 값: 42
💡 설명
- std::make_unique<int>(42)로 안전하게 동적 메모리 할당.
- std::move(ptr)을 사용하여 소유권 이전 가능.
🔹 (3) std::shared_ptr (참조 카운트 기반 스마트 포인터)
- 여러 개의 std::shared_ptr가 같은 객체를 공유 가능.
- 참조 카운트(reference count)를 사용하여 마지막 shared_ptr이 삭제될 때 자동으로 메모리 해제.
💡 예제: std::shared_ptr 사용법
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(100);
std::shared_ptr<int> p2 = p1; // 같은 객체를 공유
std::cout << "p1의 값: " << *p1 << ", 참조 카운트: " << p1.use_count() << std::endl;
std::cout << "p2의 값: " << *p2 << ", 참조 카운트: " << p2.use_count() << std::endl;
return 0;
}
🔹 출력 결과
p1의 값: 100, 참조 카운트: 2
p2의 값: 100, 참조 카운트: 2
💡 설명
- std::shared_ptr를 사용하면 여러 개의 포인터가 같은 객체를 관리할 수 있음.
- use_count()를 통해 참조 카운트를 확인 가능.
📌 4. 이동 시멘틱스 (move semantics)로 불필요한 복사 방지
🔹 (1) 기존의 복사 방식 문제
- 객체를 반환할 때 값 복사가 발생하여 성능 저하.
🔹 (2) 이동 시멘틱스 (std::move())를 활용한 최적화
- 객체를 이동하여 복사를 방지하고, 기존 리소스를 재사용.
💡 예제: 이동 생성자 활용
#include <iostream>
#include <vector>
class MyClass {
public:
std::vector<int> data;
MyClass(std::vector<int> d) : data(std::move(d)) {} // 이동 시멘틱스 적용
};
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
MyClass obj(std::move(v)); // v의 데이터가 obj로 이동됨
std::cout << "obj의 데이터 크기: " << obj.data.size() << std::endl;
std::cout << "이동 후 v의 크기: " << v.size() << std::endl;
return 0;
}
🔹 출력 결과
obj의 데이터 크기: 5
이동 후 v의 크기: 0
💡 설명
- std::move(v)를 사용하여 벡터의 데이터를 obj로 이동.
- 이동 후 v의 크기가 0이 되어, 복사가 발생하지 않았음을 확인 가능.
📌 5. 정리
개념 | 설명 |
auto, decltype | 타입을 자동으로 추론하여 변수 선언을 단순화 |
nullptr | 기존 NULL보다 안전한 포인터 처리 방식 |
스마트 포인터 | std::unique_ptr, std::shared_ptr을 활용한 안전한 메모리 관리 |
이동 시멘틱스 | std::move()를 활용하여 불필요한 복사를 방지 |