SturdyCobble's Study Note

[프로그래밍] 5.3 클래스(3) - 생성자와 소멸자 본문

휴지통/['19.06-'20.07]프로그래밍101

[프로그래밍] 5.3 클래스(3) - 생성자와 소멸자

StudyingCobble 2019. 11. 9. 14:22

NOTICE : 독학인 만큼, 잘못된 내용이 있을 수 있습니다. 내용 지적은 언제나 환영합니다.

더 이상 이 블로그에는 글을 올리지는 않을 예정입니다. 그렇지만 댓글을 달아주시면 최대한 답변드리고자 노력하겠습니다.


※이 글은 프로그래밍 언어에 대한 기초적인 이해를 가정하고 있습니다. 최소 프로그래밍 언어 하나 정도를 약간이라도 접해보시는 것을 추천합니다. 또한, 이 글은 심화 내용은 되도록 피하여 서술했습니다.

프로그래밍으로 클래스를 사용하다보면, 인스턴스에 사용된 변수들을 초기화하거나, 초기 설정을 해야할 상황이 생깁니다. 이럴 때 생성자라는 개념을 활용할 수 있습니다. 생성자는 말 그대로 인스턴스가 생성될 때 자동으로 호출되는 메소드를 말합니다. 또한, 이에 대응되는 개념으로 인스턴스가 소멸할 때(메모리에서 해제될 때) 호출되는 소멸자라는 개념도 있습니다.

 

 생성자와 소멸자는 다음과 같은 이름을 가진 메소드로 정의됩니다.

  C++ Java Python
생성자 Constructor

(클래스 이름과 동일) ex) 클래스 명이 Test이면 Test

__init__
(언더바 2개가 양 끝에 위치)

소멸자 Destructor ~(클래스 이름) ex) ~Test finalize __del__

 

Python예제를 한번 봐봅시다.

class Test:
    def __init__(self,name : str ,height : int):
        self.name = name
        self.height = height
    def strAdd(self):
        return self.name + " " + str(self.height)
    def __del__(self):
        print("I've done all of my work...")

test = Test("James",170)
print(test.strAdd())

--------------------Result---------------
James 170
I've done all of my work....

보시다시피 함수에 인수를 전달하듯이 인스턴스를 생성하며 인수를 전달해 주니, 자동으로 생성자가 이를 초기화 해서 strAdd메소드를 호출할 때 두 값이 더해져서 반환 된 것을 확인할 수 있습니다. 그리고 할 일을 마친 소멸자는 마지막에 한 문장을 출력하게 되었죠.

 

 위 코드에서 test = Test()라고만 써서 인수를 전달하지 않으면 다음과 같이 에러가 발생합니다.

I've done all of my work...
Traceback (most recent call last):
  File "E:\User File\My Files\Programming\Python\test.py", line 10, in <module>
    test = Test()
TypeError: __init__() missing 2 required positional arguments: 'name' and 'height'

(혼란을 틈타 호출된 소멸자)

 

인수가 필요하다는 에러가 뜨게 됩니다.

 

C++에서는 다음과 같이 써볼 수 있습니다.

# include <iostream>

using namespace std;

class TestCpp{
  public:
    int num1;
    int num2;
    
    TestCpp(int m_num1, int num2){
      num1 = m_num1;
  	  TestCpp::num2 = num2;
    }
    ~TestCpp(){
      printf("I did my best...");
    }
    int addTwo(){
      return num1 + num2;
    }
};

int main(){
	TestCpp test1(10,20);
	printf("%d\n", test1.addTwo());
}


--------------Result---------------------------
30
I did my best...

 

참고로 생성자와 소멸자에 Type이 없음에 주의해야 합니다! (또한 public으로 설정해야 합니다.) 위에서 argument로 사용된 변수랑 클래스 자체에 있던 변수랑 이름이 겹치므로 m_과 같이 접두사를 붙여 이름을 다르게 해주거나, 범위 지정 연산자 ::으로 지정해서 사용해야 합니다.(관련 내용은 이후 다루겠습니다)

 

그렇다면 과연 자바는 어떨까요??

public class HelloWorld {
    public static void main(String[] args) {
        TestClass test1 = new TestClass(10);
        test1.saySomething();
    }
}

class TestClass{
  public int num1;
  public TestClass(int num1){
    this.num1 = num1;
  }
  public void finalize(){
    System.out.println("Finalize called");
  }
  public void saySomething(){
    System.out.println(num1);
  }
}

(생성자는 타입이 없다는 점에 유의해주세요! 또한, public으로 생성자와 소멸자가 정의된 점을 유의해주세요!)

 

Python과 C++의 실행 결과를 바탕으로 아래와 같은 결과를 예상하실겁니다.

10
Finalize called

하지만, 일반적으로 다음의 결과를 얻을 것입니다.

10

이는 자바의 경우 Garbage Collection, 굳이 한글로 뜻을 바꿔보자면 쓰레기 수집이라는 것을 통해 필요없는 메모리를 해제할 때, finalize가 호출되는 데, 이 상황에서는 메모리 해제가 일어나지 않았음을 나타냅니다. 즉, 항상 호출되는 것은 아닙니다. 그렇다면, 어떻게 이 인스턴스를 쓰레기라고 인식하게 만들 수 있을까요?

 

 굳이 항상 호출하고 싶다면, 위 코드에서 HelloWorld 클래스를 다음과 같이 바꾸면 됩니다.

public class HelloWorld {
    public static void main(String[] args) {
        TestClass test1 = new TestClass(10);
        test1.saySomething();
        test1 = null;
        System.gc();
    }
}

(System.gc()는 가비지 컬렉션을 진행하는 가비지 컬렉터를 호출합니다.)

test1에 null을 넣는 순간, test1은 TestClass의 인스턴스가 아니게 되어, 마치 빈 깡통과 같은 상태가 됩니다. 이 때 가비지 컬렉션을 호출한다면 다음과 같은 결과가 얻어집니다.

 

10
Finalize called

 

 

자바와 달리 C++은 이러한 가비지 컬렉션이 없기에, 소멸자는 인스턴스가 소멸될 때 수동으로 메모리를 해제하는 역할을 하기 위해 주로 사용됩니다. 그러나, 자바는 그럴 일이 별로 없기 때문에, 위와 같은 상황이 벌어집니다.

 

 

 한편, C언어와 Java는 다음과 같은 생성자 오버로딩을 지원합니다. (Overloading) 이름은 같으나 받는 인수 개수가 다름에 유의하세요!

# include <iostream>

using namespace std;

class TestCpp{
public:
  TestCpp(int m_num1, int num2){
    printf("2 int argument\n");
  }
	TestCpp(int m_num1, int num2, int num3){
    printf("3 int argument\n");
  }
};

int main(){
	TestCpp test1(10,20);
	TestCpp test2(10,20,30);
}

-------------Result------------
2 int argument
3 int argument

public class HelloWorld {
    public static void main(String[] args) {
        TestClass test1 = new TestClass(10,20);
        TestClass test2 = new TestClass(10,20,30);
    }
}

class TestClass{
  public int num1;
  public TestClass(int num1, int num2){
    System.out.println("2 int");
  }
  public TestClass(int num1, int num2, int num3){
    System.out.println("3 int");
  }
}

-------------Result-------------
2 int
3 int

위와 같은 오버로딩은 상황에 따라 클래스를 여러가지로 초기화하는데 유용할 것입니다. Python의 경우 위와 같은 형식은 불가능하나, 가변 인수를 받음으로서 비슷한 코드를 작성할 수 있습니다. 아니면 위와 비슷한 코드를 아래와 같이 작성해볼 수도 있습니다.

 

class Test:
    def __init__(self, ar1 : int, ar2 : int = None):
        if ar2 == None:
            print("int 1")
        else:
            print("int 2")

test1 = Test(1)
test2 = Test(1,2)
---------Result--------------
int 1
int 2
Comments