본문 바로가기

서적 정리/Effective C++56

19.클래스 설계는 타입 설계와 똑같이 취급하자 새로운 클래스를 정의한다는 것은 새로운 타입을 하나 정의하는 것과 같다. 그냥 클래스 설계자가 아니라 타입(type) 설계자라는 막강한 권위를 갖고 있다라는 뜻이다. 함수와 연산자를 오버로드하고, 메모리 할당 및 해제를 제어하며, 객체 초기화 및 종료처리를 정의하는 작업을 한다. 좋은 타입이란 문법(syntax)이 자연스럽고, 의미구조(semantics)가 직관적이며, 효율적인 구현이 한 가지 이상 있어야 한다. 효과적인 클래스를 설계를 할 때 여러가지 고려사항이 있다. 1.새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이뤄져야 하는가? 이 부분이 어떻게 되느냐에 따라 생성자 및 소멸자의 설계가 바뀐다. 메모리 할당 함수(8장 참고)(operator new, operator new[], operator.. 2021. 12. 14.
18.인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 제대로 쓰기엔 쉽고 엉터리로 쓰기엔 어려운 인터페이스를 개발하려면 사용자가 실수할 만한 종류를 알고있어야한다. 만약 날짜를 나타내는 클래스에 넣을 생성자를 생성한다고 가정해보자. class Date { public: Date(int month, int day, int year); }; 별 문제 없어 보이지만, 사용자가 저지를 수 있는 오류는 존재한다. 매개변수 순서가 잘못될 수있고, 월과 일에 해당하는 숫자가 범위에 벗어날 수 있다. Date d(30, 3, 1995); //3, 30 1995여야함 Date d(3, 40, 1995); //40일은 존재하지 않음 오타 혹은 실수로 저지를법한 실수다. 어처구니 없는 코드가 컴파일되는 상황을 막는 방법으로 타입 시스템을 들 수있다. struct Day { e.. 2021. 12. 14.
17.new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자 처리 우선순위를 알려 주는 함수가 있고, 할당된 객체에 대해 어떤 우순순위에 따라 처리를 적용하는 함수가 하나 있다고 가정해보자 int Priority(); void ProcessWidget(std::tr1::shared_ptr pw, int priority); 자원 관리에는 객체를 사용하는 것이 좋다(13장 참고)는 점을 살려 ProcessWidget 함수는 동적 할당된 Widget 객체에 대한 스마트 포인터를 사용하도록 만들어졌다. 이렇게 만들어진 ProcessWidget 함수를 사용한다면 ProcessWidget(new Widget, Priority()); 컴파일 자체도 안되는 코드다. 포인터를 받는 tr1::shared_ptr의 생성자는 explicit(*주 1)로 선언되어 있기 때문에, new .. 2021. 12. 11.
16.new 및 delete를 사용할 때는 형태를 반드시 맞추자 new 연산자를 사용하여 표현식을 꾸미게 되면(new로 어떤 객체를 동적 할당하면), 이로 인해 두 가지 내부 동작이 진행된다. 1.메모리가 할당된다. 이때 operator new라는 함수가 쓰인다(49장, 51장 참고). 2.할당된 메모리에 대해 한 개 이상의 생성자가 호출된다. string* str1 = new string; //생성자 한 번 호출 string* str2 = new string[10]; //생성자 10번 호출 delete 표현식을 사용하는 경우 역시 두 가지의 내부 동작이 진행된다. 1.기존에 할당된 메모리에 대해 한 개 이상의 소멸자가 호출 string* str1 = new string; //생성자 한 번 호출 string* str2 = new string[10]; //생성자 10번.. 2021. 12. 11.
15.자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 스마트 포인터(auto_ptr, tr1::shared_ptr)는 스마트 포인터 타입의 객체를 반환한다. std::tr1::shared_ptr pInv; int daysHeld(const Investment* pi); int days = datysHeld(pInv); //에러 즉, 실제 자원으로 변환할 방법이 필요하다. 스마트 포인터를 실제 자원으로 변환할 일반적인 방법은 두 가지가 있다. 1.명시적 변환(explict conversion) 스마트 포인터에는 명시적 변환을 수행하는 get 멤버 함수가 있다. get 함수를 사용하면 포마트 포인터 객체에 들어있는 실제 포인터의 사본을 얻을 수 있다. int days = daysHeld(pInv.get()); 2.암시적 변환(implict conversion).. 2021. 12. 11.
14.자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 자원 관리 클래스의 주축을 이루는 아이디어인 자원 획득 즉 초기화(RAII)기법, 힙 기반 자원에 의해 이 아이디어를 명쾌하게 적용한 스마트 포인터가 있다. 하지만 세상의 자원이 모두 힙에서 생기지 않는다는 현실이 있다. 그러기에 자원 관리 클래스를 스스로 만들어야 할 필요성이 있다. Mutex 타입의 뮤텍스 객체를 조작하는 C의 API를 사용하고 있다고 가정해보자. C의 API에서 제공하는 함수 중엔 lock 및 unlock이 있다. void lock(Mutex* pm); //pm이 가리키는 뮤텍스에 잠금을 건다. void unlock(Mutex* pm); //pm이 가리키는 해당 뮤텍스의 잠금을 해제한다. 이전에 걸어 놓은 뮤텍스 잠금을 잊지 않고 풀어 줄 목적인 뮤텍스 잠금을 관리하는 클래스를 하나.. 2021. 12. 11.
13.자원 관리에는 객체가 그만! 프로그래밍에서 자원(resource)이란, 사용을 마치고 난 후엔 시스템에 돌려줘야 하는 모든 것을 일컫는다. 돌려주지 않는 순간 메모리 누수가 발생한다. 동적 할당이 대표적인 예인데 포폴작업하다보면 동적할당을 해제하지 않으면 점점 컴이 느려진다고 느껴지는데 메모리 할당 문제랑 연관이 있는진 몰라도 메모리 해제의 필요성을 느낀다. 자원에는 파일 서술자(file descriptor), 뮤텍스 잠금(mutex lock), 그래픽 유저 인터페이스(GUI)에서 쓰는 폰트(font)나 브러시(brush)도 자원이다. 어쨌든 메모리를 사용한 후엔 반드시 해제해야 한다. 투자를 모델링해 주는 클래스 라이브러리르 가지고 어떤 작업을 한다고 가정해보자. 이 라이브러리는 Investment라는 최상위 클래스가 있고, 이 .. 2021. 12. 10.
12.객체의 모든 부분을 빠짐없이 복사하자 STL이나 캡슐화한 객체 지향 시스템 중 설계가 잘 된것을 보면 객체를 복사하는 함수가 딱 둘만있다. 내가 STL의 vector구현할 때도 복사 생성자와 복사 대입 연산자 두 개만 구현했고 다른 곳에서도 두 개만 있다. 이 둘을 통틀어 객체 복사 함수(copying function)이라고 하고, 컴파일러가 필요에 따라 만들어내기도 한다(5장 참고). 복사를 하면서 가장 기본적인 요구는 당연히 빠짐 없이 데이터를 복사한다는 것이다. 고객을 나타내는 클래스가 있다고 가정해보자. void logCall(const string& funcName); //로그 기록 내용만 만들어냄 class Customer { public: /* @brief : 복사 생성자 */ Customer(const Customer& rhs.. 2021. 12. 10.
11.operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 자기대입(self assignment) 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것을 말한다. class Widget { ... }; Widget w; w = w; //자기대입 솔직히 고의로 자기대입을 할 경우가 전혀 없다고 생각한다. 왜냐하면 자신에게 자신을 대입한다고 한들 변화가 없기 때문인데, 나도 모르게 자기대입을 하는 경우가 생기고 이게 눈에 잘 띄지도 않는다. 본인도 모르게 자기대입을 하는 경우를 생각해보자 int a[5]; for (int i = 0; i < 5; i++) for (int j = 0; j < 5; j++) a[i] = a[j]; //i == j 일 때 자기대입 발생 2중 for문에서 만약 i와 j가 같은 상황일 때 자기대입이 발생할 것이고 *px = *py; //.. 2021. 12. 10.