클래스 (Class)

C++ 에서 클래스란 객체 지향 프로그래밍의 추상화를 사용자가 정의할 수 있는 사용자 정의 타입으로 구현한 것이다. C++ 의 구조체와 마찬가지로 멤버로 변수와 함수를 가질 수 있다.

C++ 에서 클래스와 구조체는 기본 접근 지정자의 차이만 있을 뿐 나머지는 동일하게 작동한다. 구조체의 기본 접근 지정자는 public, 클래스의 기본 접근 지정자는 private 이다.

클래스는 class 키워드를 사용하여 정의한다.

class ClassName
{
Access Specifier:
	MemberValType MemberValName;
	...
	
	MemberFunc();
	
}
// Student 클래스
class Student
{
public:
	char Name[10];
	int Grade = 4;
	double GPA = 3.8;
	
	int Func1() {};
	void Func2() {};
};

클래스 객체 생성

클래스의 정의는 클래스의 형태(틀)만 정의한 것이다. 이 형태를 가지고 객체를 생성할 수 있다.

ClassName InstanceName;
Student NewStudent; // Student 클래스 객체 NewStudent 생성

이렇게 메모리에 대입되어 생성된 클래스 타입의 객체를 인스턴스(Instance)라고 한다. 하나의 클래스에서 여러 개의 인스턴스를 생성할 수 있으며 인스턴스는 독립된 메모리 공간에 자신만의 멤버 변수를 가지지만, 멤버 함수는 모든 인스턴스가 공유한다.

클래스 멤버 접근

클래스에서 멤버 연산자 . 를 이용해 외부에서 클래스의 멤버에 접근할 수 있다.

InstanceName.MemberName;
#include <iostream>

// Student 클래스 정의
class Student
{
public:
    char Name[10];
    int Grade = 0;
    double GPA = 0.0;

    int Func1() {};
    void Func2() {};
};

int main(void)
{
    // Student 객체 NewStudent 생성
    Student NewStudent;

    // NewStudent 객체 멤버 접근
    NewStudent.Grade = 90;
    NewStudent.GPA = 3.8;

    std::cout << NewStudent.Grade << std::endl;
    std::cout << NewStudent.GPA << std::endl;

    return 0;
}
90
3.8

접근 지정자

접근 지정자는 객체 지향 프로그래밍의 정보 은닉을 위한 키워드읻. C++에서는 다음 3가지 유형의 접근 지정자를 제공한다.

public 어디서는 직접 접근이 가능
protected 상속받는 파생 클래스에서는 public 처럼 취급되지만, 외부에서는 직접 접근 불가능
private 상속받는 파생 클래스를 포함해 외부에서 직접 접근 불가능

클래스의 기본 접근 지정자는 private 으로 접근 지정자를 명시하지 않으면 private 으로 선언된다.

#include <iostream>

class Student
{
    // 기본 접근제어 지시자
    int Default_Grade;

    // public 으로 선언
public:
    int Public_Grade;

    // private 으로 선언
private:
    int Private_Grade;

    // protected 는 이후 상속에 대한 글에서 다루겠습니다.
protected:

};

int main(void)
{

    Student NewStudent;

    // public - 외부에서 직접접근 가능
    NewStudent.Default_Grade = 95;

    // private - 외부에서 직접접근 불가능
    NewStudent.Public_Grade = 95; // 오류!

    // 기본 접근제어 지시자 -> private
    NewStudent.Private_Grade = 95; // 오류!

    return 0;
}

생성자 (Constructor)

C++ 클래스의 생성자는 클래스의 객체가 생성될 때 자동으로 호출되는 멤버 함수이다. 일반적으로 생성자는 클래스의 멤버 변수를 초기활하거나 클래스를 사용하는데 필요한 초기 설정이 필요한 경우 사용한다.

생성자는 다음과 같은 특징을 갖는다.

class Example
{
public:
	// 생성자 (Constructor)
	Example() 
	{
		Value = 0;
	}

private:
	int Value;

};

클래스의 생성자는 일반 멤버 함수와 마찬가지로 오버 로딩이 가능하다.

class Example
{
public:
	// 생성자 (Constructor)
	Example() 
	{
		Value = 0;
	}

	Example(int v)
	{
		Value = v;
	}

private:
	int Value;

};

기본 생성자 (Default Constructor)

객체는 클래스의 메모리 공간의 할당 이후 생성자의 호출까지 이루어져야 객체라 할 수 있다. 다시 말하면 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다.

아래의 코드에서 Example 클래스의 생성자를 따로 만들어 주지 않았지만 객체가 생성된 것을 확인할 수 있다.

#include <iostream>

class Example
{
public:
	void Func()
	{
		std::cout << "Example 객체" << std::endl;
	}

private:
	int Value;

};

int main(void)
{
	Example NewExample;
	NewExample.Func();

	return 0;
}
Example 객체

이처럼 생성자가 정의되지 않은 클래스는 C++ 컴파일러가 자동으로 기본 생성자를 생성해준다. 기본 생성자는 인자가 없고, 내부적으로 아무 작업도 하지 않는다.

class Example
{
public:
	// 생성자가 없으면 다음과 같은 기본 생성자가 삽입된다.
	Example()	{}

	void Func()
	{
		cout << "Example 객체" << endl;
	}

private:
	int Value;

};

만약 클래스의 생성자가 정의되어 있다면 컴파일러는 기본 생성자를 생성하지 않는다.

#include <iostream>

class Example
{
public:
	// 생성자 정의
	Example(int v)
	{
		Value = v;
	}

	void Func()
	{
		std::cout << "Example 객체" << std::endl;
	}

private:
	int Value;

};

int main(void)
{
	// 생성자가 정의되었다면 기본 생성자는 자동으로 생성되지 않습니다.
	Example NewExample = Example(); // 오류!

	return 0;
}

생성자의 호출

사용자가 직접 생성자를 명시적으로 호출할 수도 있다.

#include <iostream>

class Example
{
public:
	Example()
	{
		std::cout << "생성자 1 호출" << std::endl;
		Value = 0;
	}

	Example(int v)
	{
		std::cout << "생성자 2 호출" << std::endl;
		Value = v;
	}

private:
	int Value;

};

int main(void)
{
	// 생성자 1 사용
	Example NewExample1 = Example();

	// 생성자 2 사용
	Example NewExample2 = Example(5);

	return 0;
}
생성자 1 호출
생성자 2 호출

소멸자 (Destructor)

C++ 클래스의 소멸자는 객체가 소멸할 때 반드시 호출되는 멤버 함수이다. 생성자가 클래스의 초기화를 돕는다면 소멸자는 클래스의 릴리즈를 돕도록 설계되어있다.

소멸자는 다음과 같은 특징을 갖는다.

class Example
{
public:
	// 소멸자 (Destructor)
	~Example() {}

private:
	int Value = 0;

};

기본 소멸자 (Default Destructor)

소멸자도 생성자와 마찬가지로 따로 정의하지 않으면 C++ 컴파일러가 내부적으로 아무 작업도 하지 않는 기본 소멸자를 자동으로 생성해준다.

#include <iostream>

class Example
{
public:
	// 소멸자가 없으면 다음과 같은 기본 소멸자가 삽입된다.
	// ~Example() {}

private:
	int Value;

};

int main(void)
{
	Example NewExample = Example();

	// 소멸자를 정의하지 않았지만 명시적으로 호출이 가능하다.
	NewExample.~Example();

	return 0;
}

소멸자의 사용

클래스의 멤버 변수들이 단순히 값 형식이라면 소멸자는 크게 필요 없지만 동적 메모리, 파일과 같은 다른 리소스라면 객체가 소멸되기 전 동적 할당된 메모리 영역을 반환하는 등 어떠한 종류의 작업을 해야 한다. 이때 소멸자를 이용할 수 있다.

class Example
{
public:
	Example(int _Size)
	{
		// 메모리 동적 할당
		Array = new int[static_cast<size_t>(_Size)];
		Size = _Size;
	}

    ~Example()
	{
		// 동적 할당한 메모리를 삭제
		delete[] Array;
	}

private:
	int* Array;
	int Size;

};

소멸자의 호출

C++ 에서 소멸자는 프로그래머가 명시적으로 호출할 수 있지만 객체 소멸시 자동으로 호출되기 때문에 명시적으로 호출하지는 않는다.

C++ 에서 소멸자의 호출 시기는 컴파일러가 알아서 처리하게 되며 객체가 선언된 메모리 영역별로 소멸자가 호출되느 시기는 다음과 같다.

| --- | --- |

#include <iostream>

class Example1
{
public:
	~Example1()
	{
		std::cout << "Example1 소멸" << std::endl;
	}

};

class Example2
{
public:
	~Example2()
	{
		std::cout << "Example2 소멸" << std::endl;
	}

};

class Example3
{
public:
	~Example3()
	{
		std::cout << "Example3 소멸" << std::endl;
	}

};

void Func(Example2 _Ex)
{
}

int main(void)
{
	Example1 NewExample1 = Example1();
	Example2 NewExample2 = Example2();
	Example3* NewExample3 = new Example3;

	std::cout << "Func 함수 호출" << std::endl;
	Func(NewExample2);
	std::cout << "Func 함수 종료" << std::endl;

	std::cout << "\\n동적할당 메모리 반환" << std::endl;
	delete NewExample3;

	std::cout << "\\nmain 함수 종료" << std::endl;
	return 0;
}
Func 함수 호출
Example2 소멸
Func 함수 종료

동적할당 메모리 반환
Example3 소멸

main 함수 종료
Example2 소멸
Example1 소멸