C++ TIL day 7

2024. 12. 24. 19:43·C++

1. 자원 관리하기

1. 스택 메모리

  • 메모리 영역이 크지는 않지만, 생존 영역을 벗어나면 자동으로 해제시켜준다.
  • 스택 메모리의 생존 주기는 { } 안이다. 밖으로 나가면 해제된다.
  • static 키워드
    • 한번 선언되면, 프로그램 종료될 때 소멸된다.
    • 함수 안에서 선언한 후 함수가 끝나더라도, 값이 사라지지 않는다.
      • 아래 예시의 경우 11, 12, 13, ... , 20 까지 출력된다.
#include <iostream>
using namespace std;

void func()
{
	static int a = 10;
	a++;
	cout << "a : " << a << "\n";
}

int main()
{
	int n = 10;
	while(n--)
		func();

	return 0;
}

2. 힙 메모리

  • 프로그램 실행 시 동적으로 할당하는 공간은 힙 메모리를 활용하게 된다.
    • new와 delete 연산자를 이용
    • 스택 메모리 영역처럼 자동으로 해제되지 않기 때문에, 코드를 짤 때 조심해야 한다.
  • 변수 하나의 동적 할당과 해제
void func()
{
	int* ptr = new int(10);
	delete ptr;
}
  • 배열의 동적 할당과 해제
    • 아래 예시는 사용자의 입력을 받아서 배열의 크기를 할당해주는 예시이다.
    • 실제로 이런식으로 사용하는 것은 위험할 수 있다.
void func()
{
	int size;
	cin >> size;

	if (size > 0)
	{
		int* arr = new int[size];

		for (int i = 0; i < size; ++i)
		{
			arr[i] = i * 2;
		}
	}
}

3. Dangling Pointer

  • 가리키고 있던 메모리 공간이 해제 되어 버려서, 포인터가 해지된 공간을 계속해서 가리키고 있는 상황을 말한다.
  • 해지된 곳을 가리키는 것은 Segment Fault (잘못된 메모리 참조) 를 발생시킬 수 있다.
  • dangling pointer를 발생시킬 수 있는 두가지 나쁜 예시
    • 메모리 공간을 공유하는 두 개의 포인터 중 한 가지만 해제하는 경우(func1)
    • 같은 공간의 메모리 해제를 두 번하는 경우(func2)
      • func2의 경우 컴파일 이전에 warning을 준다. (초기화 되지 않은 메모리를 사용한다)
      • 디버거로 실행한 경우 아래의 오류가 뜬다.

void func1() 
{
    int* ptr = new int(40);
    int* ptr2 = ptr;

    cout << "ptr adress = " << ptr << endl;
    cout << "ptr2 adress = " << ptr2 << endl;
    cout << *ptr << endl;

    delete ptr;

    cout << *ptr2 << endl; //40이 아닌 이상한 값이 출력된다.
}

void func2() 
{
    int* ptr = new int(30);
    cout << "Value: " << *ptr << endl;
    delete ptr;            
    delete ptr; //이 경우 컴파일러에서도 warning 을 주고, 컴파일 후 비정상적으로 종료된다.
}

4. Smart Pointer

  • 위에서 언급한 힙 메모리에서 관리해야 하는 데이터들은 메모리 해제가 굉장히 중요하기 때문에 코드를 잘 관리하여 메모리 할당과 해제를 잘 해줘야 한다는 어려움이 있다.
  • 이러한 어려움을 해결해 주는 것이 reference counter를 이용한 smart pointer이다.
    • smart pointer는 자신을 참조하고 있는 포인터의 갯수가 0이 되는 순간 자동적으로 메모리를 해제해주는 방식으로 동작한다.
    • #include <memory> 를 해서 사용할 수 있다.
  • 종류
    • unique_ptr
      • reference counter가 최대 1인 smart pointer이다.
      • 그렇기 때문에 복사 혹은 대입이 불가능하다.
      • move를 통해서 소유권을 이전시켜주는 방식은 가능하다.
    • shared_ptr
      • reference counter가 N개가 될 수 있는 smart pointer이다.
      • 현재 reference counter를 볼 수 있는 use_count()와
      • 현재 포인터를 초기화하는 reset() 함수가 존재한다.
  • unique_ptr 예시
#include <iostream>
#include <memory>
using namespace std;

class MyClass
{
public:
	MyClass(int val) :value(val)
	{
		cout << "MyClass 생성: " << value << "\n";
	}
	~MyClass()
	{
		cout << "MyClass 소멸" << value << "\n";
	}
	const void Display()const
	{
		cout << "value: " << value << "\n";
	}
private:
	int value;
};

int main()
{
	unique_ptr<MyClass> myObject = make_unique<MyClass>(10);
	cout << "--------\n";
	myObject->Display();

	unique_ptr<MyClass> newObject = std::move(myObject);

	if (myObject == nullptr)
	{
		cout << "empty object\n";
	}
	newObject->Display();
	cout << "--------\n";
	return 0;
}

실행 결과

  • shared_ptr 예시
#include <iostream>
#include <memory>
using namespace std;

class MyClass
{
public:
	MyClass(int val) :value(val)
	{
		cout << "MyClass 생성: " << value << "\n";
	}
	~MyClass()
	{
		cout << "MyClass 소멸" << value << "\n";
	}
	const void Display()const
	{
		cout << "value: " << value << "\n";
	}
private:
	int value;
};

int main()
{
	shared_ptr<MyClass> myObject = make_shared<MyClass>(10);
	cout << "--------\n";	

	shared_ptr<MyClass> newObject1 = myObject;
	shared_ptr<MyClass> newObject2 = newObject1;

	cout << "ref count : " << myObject.use_count() << "\n";

	newObject1->Display();
	newObject2->Display();
	
	newObject2.reset();
	cout << "ref count : " << myObject.use_count() << "\n";

	cout << "--------\n";
	return 0;
}

실행 결과

5. 얕은 복사와 깊은 복사

  • 얕은 복사
    • 포인터 연산에서 실제 값이 복사되는 것이 아니라 위치가 공유되는 것이다.
    • 포인터 연산에서 대입연산자 주의하자
      • 아래 예시처럼 대입하는 경우 위치가 공유되는 것이다!
      • 위치가 공유된다는 것은 B라는 int 타입의 포인터가 만들어진 후 A 포인터가 가리키는 주소값을 가리키게 만들었다 라는 의미이다.
      • 아래의 예시에서 주석처리한 곳에 ...된 곳을 보면 A와 B의 주소값이 같은 것을 볼 수 있다.
      • 즉 A와 B는 같은 곳을 가리킨다는 의미이다.
#include <iostream>
using namespace std;

int main() 
{
    int* A = new int(30);

    int* B = A;

    cout << *A << " " << A << "\n"; //30, ...50
    cout << *B << " " << B << "\n"; //30, ...50

    delete A;

    cout << *B << endl; //dangling pointer

    return 0;
}
  • 깊은 복사 
    • dangling pointer 방지하기
    • 주석처리한 곳에 ... 된 곳이 주소값을 말하는데, A와 B의 주소값이 다른 것을 볼 수 있다.
    • 즉, 30이라는 값을 가리키는 새로운 메모리 공간을 할당한 것
#include <iostream>
using namespace std;

int main() 
{
    int* A = new int(30);
    int* B = new int(*A);

    cout << *A << " " << A << "\n"; // 30, ...10
    cout << *B << " " << B << "\n"; // 30, ...90

    delete A;

    cout << *B << endl; // 30

    delete B;

    return 0;
}

6. 숙제

  • 숙제1
    • 소멸자에 delete 넣어주기
  • 숙제2
#include <iostream>
#include <memory>
#include <string>
using namespace std;

class Logger
{
public:
	Logger()
		:logCount(0)
	{}
	~Logger()
	{
		cout << "Logger instance destroyed.\n";
	}
	void LogInfo(string message);
	void LogWarning(string message);
	void LogError(string message);
	void ShowTotalLogs();
	
private:
	int logCount;
};

void Logger::LogInfo(string message)
{
	logCount++;
	cout << "[INFO]: " << message << "\n";
}

void Logger::LogWarning(string message)
{
	logCount++;
	cout << "[WARNING]: " << message << "\n";
}

void Logger::LogError(string message)
{
	logCount++;
	cout << "[ERROR]: " << message << "\n";
}

void Logger::ShowTotalLogs()
{
	cout << "Total logs recorded: " << logCount << "\n";
}

int main()
{
	unique_ptr<Logger> logger = make_unique<Logger>();

	logger->LogInfo("System is starting");
	logger->LogWarning("Low disk space");
	logger->LogError("Unable to connect to the server");
	logger->ShowTotalLogs();
	
	return 0;
}

 

 

2. 템플릿

1. 함수 오버로딩

  • 이름이 같은데, 매개변수가 다른 함수
    • 매개변수의 타입이 다르거나
    • 매개변수의 갯수가 다르거나
  • 함수 오버로딩의 순서
    1. 정확한 타입 일치
    2. 암묵적인 타입 변환
      • 오버로딩된 함수 매개변수 타입이 (int, int) 와  (float, float) 인 경우
      • 우리가 해당 함수에 int float 타입 변수를 넣었다면, 두 번째로 넣은 변수인 float를 int로 형변환 하여 (int, int) 함수를 호출한다.

2. 템플릿

  • 숙제
    • 파라메터가 boolean 일 때 컴파일 에러 발생시켜야 하는데, 이대로 구현한 결과 에러가 발생하지 않아
    • bool 타입 템플릿 특수화와 = delete; 를 통해 에러를 발생시키도록 하였다.
#include <iostream>
using namespace std;

template <typename T>
T Add(T a, T b)
{
	return a + b;
}

//컴파일 에러 발생시키기 위한 처리
template<>
bool Add(bool a, bool b) = delete;

int main() {
    // 정수 더하기
    cout << "3 + 5 = " << Add(3, 5) << endl;

    // 실수 더하기
    cout << "2.5 + 4.3 = " << Add(2.5, 4.3) << endl;

    // 문자열 합치기
    cout << "\"Hello, \" + \"World!\" = " << Add(string("Hello, "), string("World!")) << endl;

    // 아래 코드는 컴파일 에러가 발생해야 함
    //cout << Add(true, false) << endl;

    return 0;
}

'C++' 카테고리의 다른 글

C++ TIL day 9  (3) 2024.12.27
C++ TIL day 8  (0) 2024.12.26
Smart Pointer 보충  (0) 2024.12.24
C++ 상속  (0) 2024.12.23
C++ TIL day 6  (0) 2024.12.23
'C++' 카테고리의 다른 글
  • C++ TIL day 8
  • Smart Pointer 보충
  • C++ 상속
  • C++ TIL day 6
gbleem
gbleem
gbleem 님의 블로그 입니다.
  • gbleem
    gbleem 님의 블로그
    gbleem
  • 전체
    오늘
    어제
    • 분류 전체보기 (184)
      • Unreal Engine (73)
      • C++ (19)
      • 알고리즘(코딩테스트) (27)
      • TIL (60)
      • CS (4)
      • 툴 (1)
  • 블로그 메뉴

    • 홈
    • 카테고리
  • 링크

    • 과제용 깃허브
    • 깃허브
    • velog
  • 공지사항

  • 인기 글

  • 태그

    motion matching
    C++
    Vector
    enhanced input system
    템플릿
    DP
    매크로 지정자
    additive animation
    map을 vector로 복사
    character animation
    gamestate
    addonscreendebugmessage
    const
    applydamage
    상속
    actor 클래스
    cin함수
    싱글턴
    blend pose
    BFS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
gbleem
C++ TIL day 7
상단으로

티스토리툴바