본문 바로가기

Effective C++/4. 설계 및 선언

(8)
항목 25: 예외를 던지지 않는 swap에 대한 지워도 생각해 보자. swap : 예외 안전성 보장(항목 29) + 자기대입 방지(항목 11) => 중요한 만큼 구현을 잘하는 것이 중요함 어떻게 구현? => 기본적으로 표준 라이브러리에서 제공하는 swap 알고리즘을 사용한다. 보면 우리가 사용하는 것과 똑같이 구현되있음 한번 호출에 복사가 3번되는 것을 볼 수 있다. 하지만 타입에 따라서는 사본 없이 복사를 할 수 있는 경우도 있는데, 이때는 이 복사 3번이 매우 불필요할 수 있다. 대표적으로 복사하면 손해보는 타입은 다른 타입의 실제 데이터를 가르키는 포인터가 주성분인 타입들 이러한 개념을 설계로 이어나간 기법이 pimpl(pointer to implementation) 관용구(idiom) 이다. 예제를 바로 보자. ([C++] pImpl(pointer to implem..
항목 24: 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자. 클래스에서 암시적 타입 변환을 지원하는 것은 일반적으로 못된 생각이다. 하지만 예외도 있는데, 대표적인 경우가 숫자 타입을 만들 때이다. 예를들면 유리수를 나타내는 클래스를 만들고 있다면, 정수->유리수의 암묵적 타입 변환은 허용하자고 판단해도 된다. 왜냐하면 C++에서의 int -> double 변환과 별반 다를게 없기 때문이다. Rational = 유리수를 나타내는 클래스 그럼 +,* 같은 기본연산은 지원해줘야 할 것이다. => 멤버함수, 비멤버함수, 비멤버-비프렌드함수 중에 뭘로 해야 할까 23에서 비멤버-비프렌드 함수를 쓰자고 배웠지만, 일단 멤버함수로 만들어보자. 이러면 왠만한 상황에선 *이 잘 될 것이다. 하지만 혼합형 수치 연산도 가능했으면 좋겠다. (mixed-mode 지원) => int와..
항목 23: 함수보다는 비멤버 비프렌드 함수와 더 가까워지자 웹브라우저를 나타내는 클래스가 하나 있다고 가정해보자. 웹브라우저면 많은 함수를 지원할 것인데, 캐시를 비우는 함수, 방문기록을 없애는 함수, 쿠키를 없애는 함수 등등… 그 중에는 이 모든 것을 한꺼번에 지원하는 함수도 있을 것이다. clearEverything()와 clearBrowser() 둘 중 누가 더 괜찮을까? 정답을 먼저 알려주자면 비멤버함수쪽이 낫다. =================================================== 객체 지향 법칙중에 이런 이야기가 있다. 데이터와 그 데이터를 기반으로 동작하는 함수는 한 데 묶여 있어야 하며 => 이 대목 때문에 멤버 함수가 더 낫다고 얘기한다. 객체 지향이 할 수 있는 만큼 데이터를 캡슐화하라고 주장하고 있지만, 멤버함수 버전 ..
항목 22: 데이터 맴버가 선언될 곳은 private 영역임을 명심하자. 데이터 맴버가 왜 public, protected가 되면 안되는지 알아보자. 일단 public이 왜 안되냐. 문법적 일관성(항목 18 참고) 만약 데이터 맴버가 public이 아니라면, 사용자 쪽에서 어떤 객체에 접근할 수 있는 유일한 수단은 맴버 함수 뿐이다. 어떤 클래스의 public 인터페이스에 있는 것들이 전부 함수뿐이라면, 그 클래스의 맴버에 접근하고 싶을 때는 무조건 괄호를 붙여야하니 이와 관련된 생각을 안할 수 있다. 접근 제어 이게 별로 와닿지 않는 사람도 있을건데, 함수를 사용하면 데이터 맴버의 접근성에 대해 훨씬 정교한 제어를 할 수 있다. 만일 어떤 데이터 맴버를 public으로 내놓았다면 모두가 이 맴버에 대해 읽기,쓰기,접근 권한을 가질 것인데, 이 값을 읽고 쓰는 함수가 있으면 ..
항목 21: 함에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자. 유리수를 나타내는 객체가 있다고 생각해보자. operator*는 값을 반환함 => 객체의 생성, 소멸에 비용이 들어감 => 그래서 이걸 참조자로 반환하고 싶다. => 가능? 참조자는 그냥 메모리공간에 붙이는 또다른 이름이다. 즉 이미 존재하는 메모리 공간에 또 다른 이름을 붙이는 것이다. 만약 참조자로 반환하고 싶으면 반드시 이미 존재하는 Rational 객체의 참조자여야 한다. 이때 이 객체에는 곱셈에 대한 결과가 저장되어야 한다. 함수 수준에서 새로운 객체를 만든 방법은 딱 2가지이다. 스택에서 만듬(지역변수) 힙에서 만듬(동적 할당 메모리) 우선 전자의 방법부터 보자. 컴파일러도 병신같은 코드라고 주의를 주고 있다. 생성자가 호출되는게 싫어서 참조자를 반환할려는데 result가 생성된다? 노답, 그..
항목 20: '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달' 방식을 택하는 편이 대개 낫다. 기본적으로 C++ : call by value 임 => 함수 매개변수는 실제 인자의 copy를 통해 초기화되며, 어떤 함수를 호출한 쪽은 그 함수가 반환한 값의 copy를 돌려받는다. copy => 복사 생성자로 만듬. 이 점 때문에 call by value == high cost 왜 high cost냐? 만약 Person이라는 base class를 상속받는 Student라는 dervied class가 있다고 생각했을 때, bool validateStduent(Student s); 이런 함수가 있다고 치자. 여기에 Student plato; bool platoIsStudent = validateStudent(plato); 여기서 매개변수 s를 초기화 하기 위해 s(plato) 이런식으로 Student의 ..
항목 19: 클래스 설계는 타입 설계와 똑같이 취급하자. 클래스 정의 => 사실 type 정의임 어떤 클래스를 설계할 때는 type을 하나 만든다고 생각하고 최대한 열심히 만들자. 클래스를 설계할 때 여러 고민들이 생기는데.. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하나? 이 부분이 어떻게 되느냐에 따라 생성자, 소멸자의 설계가 바뀐다. 그 뿐 아니라 메모리 할당 함수(operator new, operator new[], operator delete, operator delete[])를 직접 작성할 경우에는 이들 함수의 설계에도 영향을 미친다. 객체 초기화는 객체 대입과 어떻게 달라야 하는가? 생성자 vs 대입 연산자의 차이를 결정짓는 요소이다. 초기화와 대입을 햇갈리지 않는 것이 가장 중요한데, 각각에 해당하는 함수 호출이 아예 다르기 때..
항목 18: 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 인터페이스 = 함수, 클래스, 템플릿 ….. 이걸 설계 신조로 삼자. "제대로 쓰기엔 쉽고, 엉터리로 쓰기엔 어렵게" 이걸 설계 신조로 삼자. 예를 들어, 날짜를 나타내는 어떤 클래스에 넣을 생성자를 설계하고 있다고 가정하자. 별 문제 없어보인다. 근데 인터페이스 설계 => 개발자의 눈이 아니라 사용자의 눈으로 봐야됨. 사용자가 어떤 실수를 저지를까? 가령 매개변수의 전달 순서가 잘못될 여지가 있다. Date d(30,3,1995); int가 될 수 있기 때문에(항목 2 참조) 타입의 안정성이 떨어진다는 단점이 있다, 타입 안전성이 신경 쓰인다면, 유효한 Month의 집합을 미리 정의해 두어도 괜찮다. 왜 굳이 객체가 아닌 함수로 했나? 이렇게 하면 안되나?? 다른 번역 단위에서 쓰일 경우 문제가 될 여..