Publish:

태그: , ,

카테고리:


🤯 언리얼을 하기 위해 C++ 기억 되살리기 프로젝트

개체지향 프로그래밍 (OOP)

1. 개체

1-1. 개체 생성 및 스택, 힙

1
2
3
4
5
Vector a;		// 스택(stack)에 메모리 만들기

Vector* b = new Vector();	// 힙 메모리에 만들기
// 힙 할당 : OS가 가진 전역 메모리를 뒤져서 메모리 줌, 근데 포인터 반환을 함. 위의 코드 변수 b엔 주소를 저장하는 것.
//	이후 스택에 b가 가진 주소를 저장함.
  • 스택
    • 예약된 로컬 메모리 공간 (작음, 일반적으로 1mb 이하)
    • 스택에 할당된 메모리는 범위를 벗어나면 사라지므로, 메모리를 할당 및 해제할 필요 없음
    • 함수 호출과 반환이 이 메모리에서 일어남
    • 스택에 큰 개체를 많이 넣으면 스택 오버플로우 발생…
    • 전역 메모리 공간 (큼!)
    • 비어 있고 연속된 메모리 블록을 찾음
    • 직접 할당 및 해제를 해야 함.. 안그러면 메모리 누수

스택을 다시 살펴보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vector AddVector(const Vector& a, const Vector& b)
{
	// &(참조)로 받는 이유?
	// 데이터 복사 안하겠다, 더 빠르니까
	// 그리고 내부에서 변경하지 않도록 const를 붙여 상수로 지정
	
	Vector result;
	result.mX = a.mX + b.mX;
	result.mY = a.mY + b.mY;
	
	return result;
}

void Foo()
{
	Vector c = AddVector(a, b);
}

스택 메모리에 먼저 Foo() 영역이 잡혀 있음.
Foo 에서 AddVector 함수 호출하므로
스택엔 또 AddVector() 영역이 잡혀짐.
AddVector 함수 작업이 끝나서 영역 벗어나면, 스택에서도 사라진다.

힙을 다시 살펴보자

1
2
3
4
5
6
7
8
9
10
11
12
13
void PrintVectors(const Vector& a, const Vector& b)
{
	Vector* result = new Vector;
	result->mX = a.mX + b.mX;
	result->mY = a.mY + b.mY;
	
	// ...
}

void Foo()
{
	PrintVectors(a, b);
}

먼저 스택에 Foo() 영역이 잡혀짐. (모든 함수는 스택에서 작동하니까)
(스택엔 함수 안에 코드가 아니라 함수에서 필요한 데이터를 담고 있는 것!)
그리고 PrintVectors() 영역 잡혀짐.
근데 new 키워드로 할당을 해주므로 힙에도 메모리가 잡혀서 (vector 니까 4바이트 2개) result 변수에 힙의 주소 줌.

그러고 나서 PrintVectors 영역 나갔는데도 힙에는 남아있음.
그래서 delete result; 코드를 함수 마지막에 넣어줘야 한다.

1-2. 개체 배열 생성, 소멸

Vector* list = new Vector[10];

스택에 list 변수 생성되고, 힙에 메모리 할당되고 메모리 주소를 변수에 넣어주게 되는데,
힙 메모리에 잡힌 사이즈는 10 * 2 (x, y니까) 해서 20개가 잡힘.

1
2
3
4
5
6
7
8
9
10
11
// 10개의 벡터 개체를 힙에 만듦
Vector* list = new Vector[10];

// 10개의 포인터를 힙에 만듦
Vector** list = new Vector*[10];

Vector* a = new Vector;
Vector* list = new Vector[10];

delete a;
delete[] list;

1-3. 생성자(Constructor), 초기화 리스트

1
2
3
4
5
6
7
8
9
10
11
12
13
class X
{
	// 이렇게 초기화 리스트를 사용하면 const도 초기화 가능
	
	const int mNameSize;
	AnotherClass& mAnother;
	
	X (AnotherClass& another)
	: mNameSize(20),
		mAnother(another)
	{
	}
}
1
2
3
4
5
6
7
8
9
10
11
class X
{
	const int mNameSize;
	AnotherClass& mAnother;
	
	X (AnotherClass& another)
	{
		mNameSize = 20;			// 에러! 이거 상수인디요
		mAnother = another;		// 에러! 이거 레퍼런스인데 또 다른걸로 바꾸려고? 안됨
	}
}

1-4. 소멸자

개체가 지워질 때 호출된다.
소멸자에는 오버로딩이 없음. 자동으로 호출되기 때문에… 매개변수 있다고 누가 호출할 수 있는 게 아님.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MyString::MyString()
		: mLength(0),
		mCapacity(15)
{
	mChars = new char[mCapacity + 1];
}

MyString::~MyString()
{
	delete[] mChars;
}

void Foo()
{
	MyString name;
}

2. const 멤버 함수

1
2
3
4
5
6
7
8
9
10
11
12
class Vector
{
	public:
		void SetX(int x);
		void SetY(int y);
		int GetX() const;		// <- 이거!
		int GetY() const;

	private:
		int mX;
		int mY;
}
  • const 변수
    • const int LINE_SIZE = 20;
    • LINE_SIZE = 10; // <- 컴파일 에러…
  • const 메서드 : 해당 개체 안의 어떠한 것도 바꾸지 않음.

멤버 변수가 변하는 것을 방지

1
2
3
4
5
6
7
8
9
10
int GetX() const
{
	return mX;		// 가능한 코드
}

void AddConst(const Vector& other) const
{
	mX = mX + other.mX;		// 컴파일 에러
	mY = mY + other.mY;		// 컴파일 에러
}

기본적으로 함수는 const로 짜는게 맞고, 멤버변수를 바꾸고자 할 땐 떼는거라고 함.

3. 구조체(struct) vs 클래스(class)

  • 기본 접근권한
    • struct
      • public
    • class
      • private
  • struct는 순수하게 데이터뿐이어야 함
    • 사용자가 선언한 생성자나 소멸자 쓰지 말자
    • 멤버변수는 전부 public으로 쓰자
    • 가상 함수 x
    • 메모리 복사가 가능함
      • memcpy()를 사용하여 struct를 char[]로, 혹은 반대로 복사할 수 있음.

이슈 및 공부한 것을 기록해두는 개인 블로그 입니다. 댓글, 피드백 환영합니다 🙂

Update:

댓글남기기