반응형
자원(resource) 관리의 중요성
- C++ 이후에 나온 많은 언어 (Java 등등) 들은 대부분은 가비지 컬렉터 (Garbage Collector - GC) 라 불리는 자원 청소기가 기본적으로 내장되어 있습니다.
- 가비지 컬렉터의 역할은 프로그램 상에서 더 이상 쓰이지 않는 자원을 자동으로 해제해 주는 역할을 합니다.
포인터 (Pointer)
- 포인터는 대게 복사를 하기 싫거나 데이터를 공유하기 위해서 사용합니다.
- C 언어에서는 malloc 과 free 함수를 지원하여 힙 상에서의 메모리 할당합니다.
- C++ 언어에서는 new 과 delete 함수를 지원하여 힙 상에서의 메모리 할당합니다.
- 포인터(*)는 레퍼런스(&)에 대응하는 C스타일 코드입니다.
- 포인터를 생성한 경우 포인터를 더 이상 사용하지 않게 되었을 때 적절하게 소멸시켜 주어야 합니다.
- C++에서 포인터를 사용할 수밖에 없는 경우는 다음과 같습니다:
- C 라이브러리와 의사소통 해야 함
- 반환값이 복사하기에도 이동하기에도 너무 무거움
- C++에서는 이 기능을 위해 '스마트 포인터'라는 개념을 도입하여
메모리 누수(memory leak)로부터 프로그램의 안전성을 보장할 수 있습니다.
스마트 포인터 (Smart Pointer)
스마트 포인터 (Smart Pointer) 종류
중요!!! 보통의 C++ 객체에 대해 스마트 포인터가 필요한 상황에서는 주로 unique_ptr을 사용
- 포인터처럼 동작하는 클래스 템플릿으로, 사용이 끝난 메모리를 자동으로 해제해 줍니다.
- C++14 이후부터 제공되는 make_unique() 함수를 사용하면 unique_ptr 인스턴스를 안전하게 생성할 수 있습니다.
- #include <memory>
- 스마트 포인터의 종류: 일반적으로 소멸할 수 있는 권리로 구분
- unique_ptr:
- 하나의 스마트 포인터만이 특정 객체를 소유, 한 곳에서만 접근됨
- move() 멤버 함수를 통해 소유권을 이전 가능하지만, 복사 불가.
- unique_ptr:
unique_ptr<int> ptr01(new int(5)); // int형 unique_ptr인 ptr01을 선언하고 초기화함.
auto ptr02 = move(ptr01); // ptr01에서 ptr02로 소유권을 이전함.
// unique_ptr<int> ptr03 = ptr01; // 대입 연산자를 이용한 복사는 오류를 발생시킴.
ptr02.reset(); // ptr02가 가리키고 있는 메모리 영역을 삭제함.
ptr01.reset(); // ptr01가 가리키고 있는 메모리 영역을 삭제함.
-
- shared_ptr:
- 하나의 특정 객체를 참조하는 스마트 포인터가 총 몇 개인지를 참조하여, 여러 군데에서 접근될 수 있음
- 참조하고 있는 스마트 포인터의 개수를 reference point라 하며, 이는 특정 객체에 새로운 shared_ptr이 추가될 때마다 1씩 증가
- 참조 개수가 0 이 되어야 가리키고 있는 객체를 할당 해제합니다.
- shared_ptr:
shared_ptr<int> ptr01 = make_shared<int>(5); // int형 shared_ptr인 ptr01을 선언하고 초기화함.
cout << ptr01.use_count() << endl; // 1
auto ptr02(ptr01); // 복사 생성자를 이용한 초기화
cout << ptr01.use_count() << endl; // 2
auto ptr03 = ptr01; // 대입을 통한 초기화
cout << ptr01.use_count() << endl; // 3
-
- weak_ptr:
- 하나 이상의 shared_ptr 인스턴스가 소유하는 객체에 대한 접근을 제공하지만, 소유자의 수에는 포함되지 않는 스마트 포인터입니다.
- 만약 서로가 상대방을 가리키는 shared_ptr를 가지고 있다면, 이러한 shared_ptr 인스턴스 사이의 순환 참조를 제거하기 위해서 사용됩니다.
- weak_ptr:
RAII(Resource acquisition is initialization): Memory Management Design Pattern
- 스코프 기준 리소스 관리(scope bound resource management)
- 이는 자원 관리를 스택에 할당한 객체를 통해 수행합니다.
- new와 delete로 관리되는 포인터는 RAII를 통해 관리될 수 없으나, 스마트 포인터는 RAII를 통해 관리될 수 있습니다.
- 더 이상 참조되지 않을 경우 소멸자가 호출되기 때문이다.
레퍼런스 (Reference)
레퍼런스 (Reference): &
- &로 표시되는 레퍼런스는 대상 변수와 같은 메모리 위치를 참조하게 되며, &가 붙은 변수의 '별명'이라고 이해하면 편리합니다.
- 참조자의 타입은 대상이 되는 변수의 타입과 일치해야 하며, 선언과 동시에 초기화되어야 합니다.
- 크기가 큰 구조체와 같은 데이터를 함수의 인수로 전달해야 할 경우에 사용할 수 있습니다.
int add_ref(int &a, int &b) { return a + b; }
+ std::ref()
- 특정 타입을 참조하는 객체를 만듭니다.
- &와 다르게 타입만 같다면 참조대상을 교체 가능합니다.
- 주로 thread의 인자 또는 bind의 인자로 넘겨줄 때 사용합니다.
상수(Const)
- 선언과 동시에 초기화되어야 하며, 주로 상수(초기화 후 수정되지 않을 변수)를 만들거나 멤버 변수를 수정하지 않는 클래스 메쏘드(getter)를 만들 때 사용합니다.
- 가독성 상승: const가 붙어있으면 함수가 멤버 변수나 메소드를 수정하지 않음을 쉽게 알 수 있습니다.
- 일반적으로 변수의 크기가 큰 경우 값 전달 보다 레퍼런스 전달이 빠릅니다.
큰 변수를 함수에 넘기면서 성능을 유지하고 싶은 경우 const &를 사용합니다.- 값 전달: 값의 크기가 작은 경우 등
- const & 전달: 값의 크기가 크며, 함수 내에서 수정되지 않음
- & 전달: 함수 내에서의 수정이 원본 변수에 반영되어야 함
상수 표현식 (Constexpr)
- C++11부터 지원하며, 일반화된 상수 표현식(Generalized constant expression)을 사용할 수 있도록 합니다.
- constexpr 변수 또는 함수의 입력값은 '상수'여야 하며, 반환값(return)은 반드시 'LiteralType'이어야 합니다.
- 상수값이 아닌 값으로 초기화 시도시 컴파일이 되지 않거나, 컴파일 타임에 실행되지 않습니다.
- LiteralType: 컴파일 타임에 해당 레이아웃이 결정될 수 있는 타입을 의미합니다.
- constexpr은 특정 연산을 컴파일 타임에 수행하기 위해 사용되며, 다음 코드도 정상 작동합니다.
#include <iostream>
template <int N>
struct A {
int operator()() { return N; }
};
int main() {
const int a = 5;
// int arr[a]; // 배열을 선언할 때 크기는 '양의 정수형 숫자'만 가능.
// 정수 상수식이 와야하는 경우,
constexpr int num = 5;
// i) 배열 선언식
int arr[num];
// ii) 템플릿 타입 인자
A<num> a;
std::cout << a() << '\n';
// iii) enum 에서 값을 지정
enum B { x = num, y, z }; // Good!
std::cout << B::x << std::endl;
}
Constexpr 함수
#include <iostream>
constexpr int Factorial(int n) {
int total = 1;
for (int i = 1; i <= n; i++) {
total *= i;
}
return total;
}
template <int N>
struct A {
int operator()() { return N; }
};
int main() {
A<Factorial(10)> a;
std::cout << a() << std::endl;
}
- 원래는 이를 구현하기 위해 다음과 같이 템플릿 메타프로그래밍을 이용해야합니다.
- 템플릿 메타프로그래밍과 관련된 내용은 여기를 클릭하세요.
#include <iostream>
template <int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
static const int value = 1;
};
template <int N>
struct A {
int operator()() { return N; }
};
int main() {
// 컴파일 타임에 값이 결정되므로 템플릿 인자로 사용 가능!
A<Factorial<10>::value> a;
std::cout << a() << std::endl;
}
#include <type_traits>
- C++ 표준 라이브러리의 <type_traits> 에서는 여러가지 템플릿 함수들을 제공합니다.
- std::is_pointer<T>::value: is_pointer 가 전달한 인자 T가 포인터면, value가 true가 되고 아니면 false가 되는 템플릿 메타 함수
- if constexpr (<조건>): 조건이 반드시 bool 로 타입 변환될 수 있어야 하는 컴파일 타임 상수식을 조건으로 갖는 템플릿 메타 함수
#include <iostream>
#include <type_traits>
template <typename T>
void show_value(T t) {
if constexpr (std::is_pointer<T>::value) {
std::cout << "포인터 이다 : " << *t << std::endl;
} else {
std::cout << "포인터가 아니다 : " << t << std::endl;
}
}
참고
- [씹어먹는 C++] C++ 의 세계로 오신 것을 환영합니다. (new, delete): https://modoocode.com/169
- [씹어먹는 C++] 객체의 유일한 소유권 - unique_ptr: https://modoocode.com/229
- [씹어먹는 C++] 자원을 공유할 때 - shared_ptr, weak_ptr: https://modoocode.com/252
- [씹어먹는 C++] constexpr와 함께라면 컴파일 타임 상수는 문제없어: https://modoocode.com/293
- [TCP School] 32) 참조자: http://www.tcpschool.com/cpp/cpp_cppFunction_reference
- [TCP School] 64) 스마트 포인터: http://www.tcpschool.com/cpp/cpp_template_smartPointer
반응형
'Study: Software(SW) > SW: Language' 카테고리의 다른 글
[C++] 템플릿 (Template) (feat. Template Meta-programming) (0) | 2022.06.14 |
---|---|
[C++] 객체지향 프로그래밍: 추상화, 캡슐화, 상속성, 다형성, 동적바인딩... (0) | 2022.06.13 |
[C++] STL 컨테이너: 자료구조를 이해하고 사용하자! (C++ 표준 라이브러리) (0) | 2022.06.13 |
[C++] 자료형 데이터 타입(Data Type): 고정 길이 정수, 형변환(Type Casting) (0) | 2022.06.13 |
[Python] 직접 파이썬 사용하기 (feat. On-line interpreter) (0) | 2022.02.20 |