effective C++

인터페이스 상속은 구현 상속과 다르다. public 상속에서, 파생 클래스는 항상 기본 클래스의 인터페이스를 모두 물려받는다. 순수 가상 함수는 인터페이스 상속만을 허용한다. 단순 가상 함수는 인터페이스 상속과 더불어 기본 구현의 상속도 가능하도록 지정한다. 비가상 함수는 인터페이스 상속과 더불어 필수 구현의 상속도 가하도록 지정한다. 인터페이스 상속과 구현 상속의 차이 상속은 크게 두 가지로 나눌 수 있다. 하나는 함수 인터페이스의 상속이고, 또 하나는 함수 구현의 상속이다. 클래스 설계자의 입장에서 보면, 멤버 함수의 인터페이스만을 파생 클래스에 상속받고 싶을 때가 있다. 어쩔 때는 함수의 인터페이스 및 구현을 모두 상속받고 그 상속받은 구현이 오버라이드가 가능하게 만들고 싶을 때도 있다. 반대로, ..
파생 클래스의 이름은 기본 클래스의 이름을 가린다. public 상속에서는 이런 이름 가림 현상은 바람직하지 않다. 가려진 이름을 다시 볼 수 있게 하는 방법으로, using 선언 혹은 전달 함수를 쓸 수 있다. 상속된 이름이 숨겨지는 것을 피해라 상속과 관련된 이름 가리기 현상을 보기 전, 이해를 돕기 위해 기본 변수에 대한 이름 가리기 규칙을 살펴보자. int x; void someFunc() { double x; std::cin >> x; } 값을 읽어 x에 넣는 위의 문장에서 실제로 참조하는 x는 전역 변수 x가 아니라 지역 변수 x이다. 이유는 안쪽 유효범위에 있는 이름이 바깥쪽 유효범위에 있는 이름을 가리기 때문이다. 컴파일러가 someFunc의 유효범위 안에서 x라는 이름을 만나면, 일단 그..
public 상속의 의미는 is-a이다. 기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되어야 한다. 왜냐하면 모든 파생 클래스 객체는 기본 클래스 객체의 일종이기 때문이다. public 상속 모형은 반드시 is-a public 상속은 is-a이다. 이 사실을 무조건 기억해야 한다. B(Base) 클래스로부터 D(Derived) 클래스를 파생시켰다면, D 타입으로 만들어진 모든 객체는 B타입의 객체이지만, 그 반대는 되지 않는다. 다시 말해 B는 D보다 더 일반적인 개념을 나타내며, D는 B보다 더 특수한 개념을 나타낸다. B타입의 객체가 쓰일 수 있는 곳에는 D타입의 객체가 쓰일 수 있다. D타입의 모든 객체는 B타입의 객체도 되기 때문이다. C++은 public 상속의 문법은 다음과 같..
컴파일 의존성을 최소화하는 작업의 배경이 되는 가장 기본적인 아이디어는 정의 대신에 선언에 의존하게 만드는 것이다. 이러한 방법으로는 핸들클래스와 인터페이스 클래스이다. 라이브러리 헤더는 그 자체로 모든 것을 갖추어야 하며 선언부만 갖고 있는 형태여야 한다. 이 규칙은 템플릿의 여부와 관계없이 동일하게 적용해야 한다. 파일 사이의 컴파일 의존성을 최대로 줄이자 C++은 헤더와 구현부를 두 파일로 분리하여 구현하는 것이 일반적이다. 프로그램의 규모가 커지면 여러 개의 파일이 존재할 것이다. 그러다 보면 파일들 사이의 컴파일 의존성이 올라가 빌드 속도도 느려지고 에러가 발생할 여지가 생긴다. class Person { public: Person(const std::string& name, const Date..
어떤 객체의 내부요소에 대한 핸들을 반환하는 것은 되도록 피해라. 캡슐화 정도를 높이고, 상수 멤버 함수가 객체의 상수성을 유지한 채로 동작할 수 있도록 하며, 무효참조 핸들이 생기는 경우를 최소화할 수 있다. 내부에서 사용하는 객체에 대한 핸들을 반환하지 마라 내부에서 사용하는 객체를 제어할 수 있는 핸들을 반환하는 것은 좋지 않다. 사각형을 사용하는 어떤 응용프로그램을 만들고 있다고 가정해 보자. 사각형은 좌측 상단과 우측 하단의 꼭짓점 두 개로 나타낼 수 있다. 이것을 추상화한 Rectangle 클래스를 만들었다. 메모리 부담을 줄이기 위해 꼭짓점을 Rectangle 자체에 넣는 것이 아니라 별도의 구조체로 관리하기로 했다. class Point { public: Point(int x, int y)..
캐스팅은 되도록 하지 마라. 특히 수행 성능에 민감한 코드에서 dynamic_cast는 더욱 하지 마라. 캐스팅이 어쩔 수 없이 필요하다면, 함수 안에 숨길 수 있는지 확인해라. 가능하다면 최소한 사용자는 자신의 코드에 캐스팅을 넣지 않는 대신 함수를 호출하여 처리할 수 있다. 구형 스타일의 캐스트를 쓰려거든 C++ 스타일의 캐스트를 선호해라. 더욱 읽기 좋은 코드가 된다. 캐스팅은 되도록 하지 마라 C++에서는 어떤 일이 있어도 타입 에러가 생기지 않도록 보장해야 한다. 타입 에러가 발생하는 원인 중 가장 큰 것이 바로 캐스트(cast)이다. 일단 캐스팅 문법을 정리하면 다음과 같다. (T) 표현식: 표현식 부분을 T타입으로 캐스팅 T(표현식): 표현식 부분을 T타입으로 캐스팅 이 두 개는 C에서 사용..
변수 정의는 늦출 수 있을 때까지 늦춰라. 프로그램도 깔끔해지고 효율도 좋아진다. 변수 정의는 최대한 늦게 변수를 정의할 때 반드시 들어가는 비용이 있다. 바로 생성자와 소멸자를 호출하는 비용이다. 가끔 사용하지도 않는 변수에 대해 비용이 드는 경우도 있다. std::string encryptPassword(const std::string& password) { using namespace std; string encryted; if(password.length() < MinimumPasswordLength) { throw logic_error("Password is too short"); } ... // 암호화 return encryted; } encryted 객체는 password의 길이가 짧아 예외..
std::swap이 정의한 타입에 대해 느리게 동작할 여지가 있다면 swap을 멤버 함수로 제공해라. 이 멤버 swap은 예외를 던지지 않도록 해야 한다. 멤버 swap을 제공했다면, 이 멤버를 호출하는 비멤버 swap도 제공해라. 클래스에 대해서는 std:swap도 특수화해야 한다. 사용자 입장에서 swap을 호출할 때, std::swap에 대한 using 선언을 넣어 준 후에 네임스페이스 한정 없이 swap을 호출하라. 사용자 정의 타입에 대한 std 템플릿을 완전 특수화하는 것은 가능하다. 그러나, std에 어떤 것이라도 추가하려 하지 마라. swap 지원 & 예외 처리 swap은 예외 안전성 프로그래밍에 없어선 안되는 함수이다. 자기 대입의 가능성에 대처하기 위한 대표적인 메커니즘으로 널리 알려져..
어떤 함수에 들어가는 모든 매개변수(this 포인터가 가리키는 객체도 포함)에 대해 타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버이어야 한다. 모든 매개변수에 대한 타입 변환은 비멤버 함수로 어떤 함수에서 모든 매개변수에 대한 암시적 변환을 지원하기 위해서는 그 함수를 비멤버 함수로 선언해야 한다. 유리수를 나타내는 클래스 Rational이 있다고 가정해 보자. class Rational { public: Rational(int numerator = 0, int denominator = 1); // not explicit = 암시적 변환 허용 int numerator() const; // getter int denominator() const; private: ... }; 유리수 클래스의 편의성을..
멤버 함수보다는 비멤버 비프렌드 함수를 자주 사용해라. 캡슐화 정도가 높아지고, 패키징 유연성도 커지며, 기능적인 확장성도 늘어난다. 멤버 함수보다는 비멤버 비프렌드 함수를 써라 멤버 함수를 사용하는 것보다 비멤버 비프렌드 함수를 사용하면 캡슐화, 패키징 유연성, 기능 확장에 대한 이점을 얻을 수 있다. 웹브라우저를 나타내는 클래스가 있다고 가정하자. 웹브라우저 클래스는 다양한 기능을 제공하는 함수들이 많을 것이다. 다운로드한 파일, 캐시를 지우는 함수, 방문 URL 기록을 없애는 함수, 시스템이 갖고 있는 쿠키를 제거하는 함수 등 이 있을 수 있다. class WebBrowser { public: ... void clearCache(); void clearHistory(); void removeCook..
hvv_an
'effective C++' 태그의 글 목록 (3 Page)