설계 및 선언 🔐

안녕하세요! 두두코딩 입니다 ✋
오늘은 타입 설계 개념에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

타입 설계자

C++에서 새로운 클래스를 정의한다는 것은 새로운 타입을 하나 정의 하는 것과 같다. 즉, C++에서는 하나의 클래스르 설계자로 그치는 것이 아니라 타입 (type)설계자라는 막강한 권한을 갖고 있다고해도 과언이 아니다. 보통, C++ 클래스를 만들 때, 함수와 연산자를 오버로드하고, 메모리 할당 및 해제를 제어하며, 객체 초기화 및 종료 처리를 정의하는 작업등이 개발자에게 달려있다.

그렇다면, 좋은 타입 설계란 어떤것일까?

좋은 타입은 문법이 자연스러워야하고, 의미구조가 직관적이며, 효율적인 구현중 한 가지 이상 가능해야한다. C++에서 클래스를 만들 때, 우리는 충분히 고민을 하고 작성하지만 이 세 가지 중 어느 것도 달성하기 힘든 것이 사실이다. 우리가 쉽게 작성하는 멤버 함수조차도 어떻게 선언되느냐에 따라 수행성능이 달라지기 때문에 우리는 신중을 기해야한다.

생각보다 설계하는 것이 어렵다. 그래서 이전의 경험을 바탕으로 몇 가지 방법을 정리해뒀는데, 효과적인 클래스(타입)을 설계하기 위해 필요한 방법들을 알아보자.

신경쓰면 좋은 부분들

🌱 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야하는가?

객체 생성과 소멸에 따라 생성자와 소멸자의 설계가 바뀌게 된다. 그뿐 아니라 메모리 할당 함수를 직접 작성하는 경우에도 함수의 설계에 영향을 미친다.

🌱 객체 초기화는 객체 대입과 어떻게 달라야하는가?

생성자와 대입 연산자의 동작 짓는 부분은 함수 호출 부분이다. 즉, 생성자는 클래스와 동일한 이름의 함수를 불러주는 것을 말한다. 반면에, 대입 연산자는 클래스를 인자로 전달받도록 한다.

🌱 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가?

객체가 값에 의해 전달된다는 것은 대입연산자를 호출하는점을 잊지말자

🌱 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?

전부는 아니지만, 클래스의 데이터 멤버의 몇 가지 조합 값만은 반드시 유효해야한다. 이런 조합을 가리키 불변속성이라고 부른다. 해당 불변속성에 따라 클래스 멤버 함수 안에서 해 주어야 할 에러 점검 루틴이 좌우된다. 특히 불변속성에는 “생성자” , “대입 연산자” , “각종 setter” 들로 구성된다. 해당 setter값과 같은 함수들은 적법한 값인지에 대한 제약을 점검하는 루틴이 포함되어져야한다.

🌱 기존의 클래스 상속 계통망에 맞출 것인가?

갖고 있는 상속을 시킨다고 하면, 기반 클래스에 따라 제약이 좌지우지 된다. 특히 멤버함수가 가상인가 비가상인가에 따라 많이 좌우된다. 특히, 소멸자 가상함수에 대해 자세한 이해가 필요하다. 해당 부분을 잘 모르겠다면 항목7를 참고하도록 하자.

🌱 어떤 종류의 타입 변환을 허용할 것인가?

C++에서 타입 변환 할 수 있는 방법은 명시적 변환과 암시적 변환 두가지로 나뉘게된다. 문자 그대로 명시적 변환은 사용자의 직접 호출로만 변환되도록 해야하며, 임의로 컴파일러가 변환하면 안된다. 해당 변환을 동작시키기위해서는 암시적 변환을 허용하지 않으면 되는데, C++11 에서는 explicit이라는 키워드를 적어주면 된다. 이전 버전에서는 opeartor 재정의를 하지 않고, 생성자로 변환될 수 있는 타입을 만들어 주지 않는 방법으로 해결했다.

구체적으로, T1 타입과 T2 타입이 존재한다고 해보자. T1 타입을 T2 타입으로 암시적 변환을 하고 싶다면, operator T2()라는 함수를 재정의해 암시적으로 변환하게 해주거나 생성자에 T2인자를 이와 같이 public T1(T2 t)전달받도록 만들면 된다. 두 가지 방법을 모두 적용하지 않고 오로지 타입에 getter로만 접근하도록 한다면 명시적 변환만 허용한다고 볼 수 있다.

🌱 어떤 연산자와 함수를 두어야 의미가 있을까?

클래스 안에 선언할 함수가 바로 여기서 결정된다. 어떤 것이 멤버함수로 적당한지, 일반함수로 적당한지 구별해야한다. 해당 부분은 항목23-26 까지 공부할 것이다.

🌱 표준 함수들 중 어떤 것을 허용하지 말아야하는가?

이 부분은 너무 자명하다. private으로 선언된 멤버는 표준 함수로 제공하면 안된다. 즉, 외부에서 사용하지 못하도록 해야한다.

🌱 새로운 타입의 멤버에 대한 접근 권한을 어느 쪽에 줄 것인가?

설계시 어떤 클래스 멤버를 public, private, protected 영역에 둘 것인가 고민해야한다. 또한, friend 함수를 비롯해 한 클래스가 다른 클래스에 중첩되도 되는가에 대해서도 고민해야한다.

🌱 선언되지 않은 인터페이스로 무엇을 둘 것인가?

설계시 만들 타입이 제공할 보장이 어떤 종류일까에 대해 고민해봐야한다는 말이다. 예를들어, 보장할 수 있는 부분은 수행 성능 및 예외 안전성 그리고 자원 사용이다. 그렇다면 클래스 구현에 있어서 해당 부분들을 보장해야되기 때문에 제약으로 작용한다.

🌱 새로 만드는 타입이 얼마나 일반적인가?

실상은 타입 하나를 정의하는 것이 아닐지 모른다. 즉, 구현은 template으로 하는 것이 좋다.

🌱 정말로 꼭 필요한 타입인가?

기존의 클래스에 대해 기능 몇 개가 아쉬워 파생 클래스를 만들고 있다면, 간단하게 비멤버 함수라든지 템플릿을 몇 개 더 정의하는 것이 낫다.

효과적인 클래스 정의

위 질문들은 만만하게 볼 수 없는 것들이다. 효과적인 클래스를 정의한다는 일이 이렇게나 어렵고 고려해야 될 점이 많다는 것을 기억하자. 하지만, 이런 역경을 뚫고 설계된 사용자 정의 클래스는 최소한 기본제공 타입정도의 든든함을 보장할 수 있다.

To Sum Up

👉 클래스 설계는 타입 설계이다. 새로운 타입을 정의하기 전에, 이번 항목에 나온 모든 고려사항을 빠짐없이 점검해보자.