프로그래밍 언어/Effective C++

[Effective C++]항목 5~8 : 생성자, 소멸자 및 대입 연산자 1

우향우@ 2023. 3. 24. 22:06

항목 5 : 클래스에서 자동으로 생성되는 메소드들

C++에서는 우리가 따로 작성하지 않으면 컴파일러가 자동으로 생성하는 기본 메소드들이 있다.

 

기본 생성자

각 멤버의 기본 생성자 호출

생성자가 없을 시 자동으로 생성 

복사 생성자

각 멤버 간 복사 생성자 호출복사 생성자, 이동 생성자, 대입 연산자 중 하나라도 있을 시 생성안됨

복사 대입 연산자

각 멤버 간 복사 대입 연산자 호출

복사 대입 연산자, 이동 생성자, 대입 연산자 중 하나라도 있을 시 생성안됨

이동 생성자

각 멤버 간 이동 생성자 호출

이동 생성자, 복사 생성자, 복사 대입 연산자, 이동 대입 연산자, 소멸자 중 하나라도 있을 시 생성안됨

이동 대입 연산자

각 멤버 간 이동 대입 연산자 호출

이동 대입 연산자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 소멸자 중 하나라도 있을 시 생성안됨

소멸자

소멸자가 없을 시 자동으로 생성, 기본 소멸 진행

 

위 사항 외에도 const, rvalue 비정적 멤버등의 사유로 인해 생성 되지 않는 경우도 있다.

 

rule of six

위의 기본 메소드들 생성 기준을 봤을때든, 논리적으로 생각하든, 별도의 데이터 관리가 필요한 클래스는 위 6가지의 적절한 데이터 관리 메소드를 구성해야하고, 데이터 관리가 필요없거나 자동으로 되는 멤버만을 가져 추가적인 관리가 필요없는 클래스는 생성자 외 나머지를 굳이 만들 필요가 없다.이를 six rule(생성자를 빼고 five rule로 부르기도 한다.) 과 zero rule(생성자는 깍두기 느낌)로 부른다. 

 

항목 6 : 자동 생성 메서드가 필요없다면 delete하자

위에서 본 기본 메서드 중 생성 조건을 만족하지만 만들고 싶지 않는 경우 해당 메서드에 = delete를 붙여 삭제할 수 있다. 대표적인 경우가 복사를 금하는 클래스에서 복사 생성이나 복사 대입을 delete하는 경우이다.

모던 C++에서는 = delete 문법을 이용하여 이를 쉽게 구현 할 수 있지만 예전 버전에서는 정의 없이 선언만 private로 작성하여 호출 시 에러가 발생하게 하거나 이를 구현한 부모 클래스를 상속받는 방식으로 메소드 사용을 금했다.

항목 7 : 자식을 자신의 포인터로 담아 쓰는 다형성 클래스의 소멸자는 가상 소멸자로 선언하자.

클래스를 동적할당하여 포인터에 담아 사용하면 언젠간 delete로 클래스를 소멸시킬 것이다. 이때 delete point를 호출하면 delete는 point의 포인터 타입에 따라 대상의 소멸자를 호출한다. 만약 포인터가 실제로 동일한 타입을 가르키고 있다면 문제가 없지만 자식의 객체를 조상 타입 포인터에 담은 것이었다면 조상 타입의 소멸자가 호출되어 문제가 생길 수 있다. 이때 해당 조상 타입의 소멸자가 가상 소멸자라면 실제로 담긴 객체 타입으 소멸자가 정상 호출되므로 문제가 생기지않는다. 따라서 이러한 사용이 있을 수 있는 클래스는 반드시 가상 소멸자로 선언해야한다.

가상함수 오버헤드

가상 소멸자가 필요할지 안할지 판단하기 귀찮다는 이유로 모든 클래스의 소멸자를 가상 소멸자로 선언하는 행위는 좋지않다. 어떤 클래스가 가상 함수를 가지게되면 그 클래스의 객체는 각 객체 메모리마다 가상함수배열을 가리키는 포인터를 가지게 되므로 객체 크기가 증가하고 실제로 들어나는 멤버와 실제 메모리 데이터 사이에 차이가 발생하여 메모리 관련 로직을 짜는게 더 어려워 질 수 있다.

 

항목 8 : 소멸자에서는 예외가 발생하지 않도록 하자 

소멸자에서 발생하는 예외는 치명적이다. 블럭 코드 안의 로직을 다 끝내지 못한다는 점도 치명적이지만 각 멤버 및 자신에 대한 기본 소멸처리도 제대로 이루어지지 않으므로 데이터 손실이 크게 날 수 있다. 이는 다음 두가지 방식으로 극복 할 수 있다.

소멸자에서 예외처리

소멸자 안에서 try catch로 예외를 처리하는 것이다. 블럭 코드안 로직도 스스로 복구할 기회가 생기며 소멸자가 정상 종료됨에 따라 기본 소멸도 문제없이 수행된다. 하지만 대부분의 예외 상황은 클래스 자체에서 처리하기보다 클래스 사용자가 외부에서 처리할 수 있도록 하는 편이 보통 상황에 적합하다. 

예외 발생 가능 부분 별도 처리

위 상황의 약점을 극복하기 위한 방법으로는 소멸자에서 아예 예외 발생 가능성을 없애려 노력하는 것이다. 예외가 발생할 수 있는 코드부분을 소멸자에서 일반 public 메소드로 옮겨 사용자가 객체 소멸전에 직접 호출하도록 하면 된다. 이때 발생한 예외는 바깥으로 던져 사용자가 처리할 수 있도록 한다.

 

보통 위 두방법을 섞어 사용자가 직접 예외 발생 가능 부분을 처리할 수 있도록 해주면서도 만약 하지않고 소멸자가 호출될 시 차안책으로 소멸자에서 try catch를 사용하며 예외 발생 가능부분을 처리하도록 하는 경우가 많다. 사용자가 만약 예외를 직접 처리하고 싶다면 제공되는 메소드를 사용하면 되고 까먹거나 이것이 필요없어 따로 사용하지 않았다면 소멸자에서 처리하면 된다.

 

 

 

참고서적

Effective C++