코딩 학습/C와 C++

C++ 기초 - 생성자와 소멸자

이개 2026. 4. 6. 12:06

메모리 릭을 추적해주는 함수

 

// Main.cpp

#include <crtdbg.h> // _CrtSetDbgFlag(), _CrtSetBreakAlloc()

class Human
{
public:
	float Height;

	float Weight;

};

int main(void)
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
		// 메모리 릭 추적.
	// _CrtSetBreakAlloc(161);
		// 메모리 릭 지점에 자동으로 중단점을 걸어주는 함수.

	Human* Park = new Human();
	
	//delete Park;
	//Park = nullptr;

	return 0;
}

 

이 코드를 실행한 후 디버그 창을 보면 메모리 릭이 나는 것을 아래와 같이 확인할 수 있다.

 

{90} 이라는 블럭에서 메모리 릭이 났다면
_CrtSetBreakAlloc(90) 함수를 써서 브레이크를 걸 수 있다.

 

디버그하면 호출 스택에서 위치를 볼 수 있다.

 

 

생성자

// Main.cpp

#include <iostream>

#include "Human.h"

int main(void)
{
	Human Human01 = Human();      
		// 생성자를 명시적으로 호출해서 객체를 만들고, 그걸 다시 Human01에 대입.
	Human* Human02 = new Human();
		// 생성자의 명시적 호출.

	Human Human03;
		// 생성자의 암시적 호출.
	Human* Human04;
		// Q. 이건 무슨 일이 벌어질까요?

	delete Human02;
	Human02 = nullptr;

	return 0;
}

 

 

Human()과 new Human()의 차이점은 스택메모리에 만드냐 힙 메모리에 만드느냐이다.

Human* Human04는 생성자 호출 안 된다.

 

Human01 Stack ✅ 명시적 main() 종료 시 자동
Human02 Heap ✅ 명시적 delete 호출 시
Human03 Stack ✅ 암시적 main() 종료 시 자동
Human04 Stack ❌ 없음 포인터만 존재, 위험!

 

스택 메모리는 main 종료 시점에 자동 소멸된다.

 

멤버변수로 다른 클래스의 객체를 갖고 있는 경우에는?

모든 멤버 변수 객체의 생성자도 호출된다.

순서: 멤버변수 생성자 호출 -> 자기 자신의 생성자 호출

 

const 변수와 참조 변수는 이니셜라이저 리스트로 초기화해줘야한다.

 

특정 매개변수를 가진 생성자를 만들면, 기본 생성자는 안 만들어진다.

 

 

소멸자

소멸자는 오버로딩 불가.

 

동적할당 메모리 소유권

// Human.cpp

#define _CRT_SECURE_NO_WARNINGS

#include "Human.h"

#include <iostream>
#include <cstring>

using namespace std;

Human::Human()
	: Height(0.0f)
	, Weight(0.0f)
	, BirthDay(20260101)
	, Name(nullptr)
{
	cout << "Human() constructor has been called." << endl;

	Name = new char[8];
	strcpy(Name, "None");
}

Human::Human(float InHeight, float InWeight, int InBirthDay)
	: Height(InHeight)
	, Weight(InWeight)
	, BirthDay(InBirthDay)
	, Name(nullptr)
{
	cout << "Human(float InHeight, float InWeight, int InBirthDay) constructor has been called." << endl;

	Name = new char[8];
	strcpy(Name, "None");
}

Human::Human(float InHeight, float InWeight, int InBirthDay, const char* InName)
	: BirthDay(InBirthDay)
{
	cout << "Human(float InHeight, float InWeight, int InBirthDay, const char* InName) constructor has been called." << endl;

	Height = InHeight;
	Weight = InWeight;

	Name = new char[strlen(InName) + 1];
	strcpy(Name, InName);
}

Human::~Human()
{
	cout << "~Human() destructor has been called." << endl;

	if (Name != nullptr)
	{
		delete[] Name;
		Name = nullptr;
	}
}

void Human::PrintInfo()
{
	cout << "Height: " << Height << " cm" << endl;
	cout << "Weight: " << Weight << " kg" << endl;
	cout << "BirthDay: " << BirthDay << endl;
	cout << "Name: " << Name << endl;
}
// Main.cpp

#include <crtdbg.h> // _CrtSetDbgFlag(), _CrtSetBreakAlloc()

#include "Human.h"

int main(void)
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	// 메모리 릭 추적.

	Human Kim = Human(182.0f, 75.0f, 19990101, "Kim");
	Human Lee(182.0f, 75.0f, 19990101);
	Human Park;

	Kim.PrintInfo();
	Lee.PrintInfo();
	Park.PrintInfo();

	// Q. 메모리 릭이 날까요? 안난다면 왜 안날까요?

	return 0;
}

 

Name 멤버 변수를 만들어서 동적할당은 했지만, 메모리 릭은 나지 않습니다. 그 이유는 main() 함수 종료 되면서 Kim 지역 변수가 사라지고, 소멸자가 자동으로 호출되게 됩니다. 소멸자에서는 Name 멤버 변수에 동적할당된 메모리 주소를 delete 해주기 때문입니다.

 

// Main.cpp

#include <crtdbg.h> // _CrtSetDbgFlag(), _CrtSetBreakAlloc()

#include "Human.h"

int main(void)
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	Human* Park = new Human(178.0f, 68.0f, 20200202, "Park");

	Park->PrintInfo();
	
	// Q. 메모리 릭이 날까요? 난다면 왜 날까요?

	return 0;
}

 

여기에선 메모리 릭이 남.

Human*로 선언한 Park도 delete 해줘야 함.

delete Park;

Park = nullptr;

 

C언어에서의 malloc() / free() Vs. C++의 new / delete

가장 큰 차이점은 new / delete는 생성자 / 소멸자가 자동으로 호출된다는 점.

 

[심화] RAII(Resource Acquisition Is Initialization)

C++에서는 객체에게 필요한 자원을 생성자에서 획득하게끔하고, 소멸자에서 획득한 자원을 해제하도록 설계하는 것이 필수적.

자원을 획득한 주체가 자원을 해제한다.

이를 RAII라고 하며, C++ 메모리 관리의 핵심 철학.

 

 

friend 키워드

다른 클래스나 다른 함수 내에서 클래스의 private 혹은 protected 멤버에 접근 가능하게 해 줌.

// Human.h

#pragma once

class Human
{
	friend class BestFriend;

public:
	Human()
		: Height(165.0f)
		, Weight(52.0f)
	{
	}

private:
	float Height;

	float Weight;

};
// BestFriend.h

#pragma once

class Human;

class BestFriend
{

public:
	void PrintYourInfo(const Human& InPerson);

};
// BestFriend.cpp

#include "BestFriend.h"

#include <iostream>
#include "Human.h"

void BestFriend::PrintYourInfo(const Human& InPerson)
{
	std::cout << "Height: " << InPerson.Height << std::endl;
	std::cout << "Weight: " << InPerson.Weight << std::endl;
}

 

전역 함수에도 선언 가능하다.

 

 

// Human.h

#pragma once

class Human
{
	friend float GetBMI(const Human& InPerson); // 멤버 함수가 아닌 전역 함수

public:
	Human()
		: Height(165.0f)
		, Weight(52.0f)
	{
	}

private:
	float Height;

	float Weight;

};
// Human.cpp

#include "Human.h"

float GetBMI(const Human& InHuman) // 관찰 포인트. 전역 함수라 범위지정자가 안쓰였습니다.
{
	return InHuman.Weight / ((InHuman.Height / 100.0f) * (InHuman.Height / 100.0f));
}

 

 

friend 지정은 단방향이로, 클래스 상호적용이 되게 하고 싶다면 각각 선언해주어야한다.