일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 상속
- virtual function
- exception
- 암시적 변환
- UE4
- reference
- 게임
- 메타테이블
- 언리얼
- implicit conversion
- effective stl
- Smart Pointer
- 루아
- Effective c++
- 예외
- operator new
- 비교 함수 객체
- c++
- 함수 객체
- 참조자
- 티스토리챌린지
- 영화 리뷰
- resource management class
- 오블완
- 영화
- 다형성
- more effective c++
- 반복자
- 스마트 포인터
- lua
Archives
- Today
- Total
스토리텔링 개발자
[More Effective C++] 32. 미래 지향적 프로그래밍 본문
728x90
항목 32. 미래 지향적인 프로그래머가 되자
미래 지향적인 프로그래밍
- 사용하고 있는 라이브러리에 함수가 새로 추가될 수 있으며
- 오버로딩도 추가로 이루어질 수 있다는 사실을 인식하고,
- 이 사실로 인해 모호한 함수 호출이 있을 수 있다는 점을 조심하자.
- 기존의 클래스 계통 구조에 새 클래스가 추가될 수 있고,
- 오늘의 파생 클래스가 내일의 기본 클래스가 될 수 있다는 사실을 인식하고 대비하자.
- 새로운 애플리케이션이 만들어질 수 있고,
- 특정 함수의 호출환경이 달라질 수 있다는 사실을 기억하고,
- 이런 함수가 어느 경우에서든 제대로 동작하도록 만들자.
- 소프트웨어 유지 보수를 만든 사람이 맡지 않는 경우가 다반사이므로,
- 다른 사람이 이해하고, 수정하고, 개선하는 데 큰 문제를 일으키지 않는 방법으로 설계하자.
주석이나 문서화 대신 C++의 특징을 사용하자.
- 예컨대 어떤 클래스가 절대로 파생되지 않는 클래스라면
- 그 클래스에 대한 헤더 파일에 주석문을 넣기보다는
- C++ 특성을 써서 아예 파생을 못하게 막아버리자.(항목 26 참조)
- 어떤 클래스는 반드시 힙에 인스턴스가 생성되어야 한다면,
- 사용자에게 문서로 알리지 말고
- 진짜로 힙에만 만들어지도록 하는 장치를 만들자.(항목 27 참조)
사용해 줄 사람을 기다리는(demand-paged) 가상함수를 만들지 말자.
- 필요한 게 아니라면 멀쩡한 함수를 가상 함수로 만들지 말자.
- 항상 그 변화는 전체 클래스와 그 클래스가 나타나는 추상화의 한도 내에서 의미를 갖도록 하자.
대입과 복사 생성은 모든 클래스에 대해 처리해 두자.
- 복사나 생성을 사용하는 사람이 없을 것 같아도 그렇게 해야 한다.
- 나중에는 언젠가 사용될 수가 있기 때문이다.
- 복사나 생성 처리가 까다로우면 복사 생성자, 대입 연산자를 private 선언 하자.
황당함 최소화의 원칙(the principle of least astonishment)
- 어떤 클래스의 연산자와 함수는, 다른 사람들이 자연스럽게 사용할 수 있는 문법과 직관적인 의미구조를 갖도록 하자.
- C++의 기본 제공 타입과 동일한 동작원리를 갖도록 하자.
- 아리송하면 int의 동작원리대로 만들어라.(when in doubt, do as the ints do.)
누구라도 일으킬 수 있는 일은 반드시 터진다.
- 누구라도 일으킬 수 있는 일이란?
- 예외를 일으킬 수 있다.
- 그 객체가 자기 자신을 대입할 수 있다.
- 객체에 값을 주기도 전에 그 객체를 사용할 수 있다.
- 객체에 값을 주고 나서 사용하지 않기도 한다.
- 터무니 없이 큰 값을 줄 수도 있다.
- 널 값이 주어진다.
- 즉, 컴파일러가 별 불평을 하지 않는 동작은 누군가가 반드시 하게 된다.
- C++ 클래스를 만들 때에는, 맞게 사용하기에는 쉽고 틀리게 사용하기에는 어렵도록 만들어야 한다.(항목 33 참조)
코드의 이식성
- 이식이 안 되는 코드보다 이식이 잘 되는 코드를 작성하기가 훨씬 어렵다.
- 이식이 안 되는 코드를 꼭 써야 할 정도로 수행 성능의 차이가 현격하게 나는 경우도 드물다.(항목 16 참조)
- 심지어 특수 목적으로 제작된 프로그램도, 시간이 어지간히 흐른 뒤에는 이식되는 것이 보통이다.
- 이식성을 갖춘 코드를 만들면
- 플랫폼이 바뀌더라도 사용할 수 있고,
- 여러분의 코드를 사용하는 사용자 기반도 많이 확보할 수 있으며,
- 개방형 시스템을 지원한다는 장점을 누릴 수 있다.
- 주력 운영체제가 망해도 얼마든지 다른 운영체제로 갈 수 있다.
변경이 필요할 때 그 변경의 영향이 제한된 부분에만 미치도록 할 것
- 힘 닿는 데까지 캡슐화를 하고, 구현에 관련된 상세한 부분은 외부에 노출시키지 말 것.
- 사용하는 개발 도구가 지원하는 기능에 맞추어 이름 없는 네임스페이스를 사용하든지,
- 파일 범위(file-scope)의 정적 변수(객체)나 정적 함수를 선언하도록 하자.(항목 31 참조)
- 가상 기본 클래스가 필요한 설계는 피하는 것이 좋다.
- 이 클래스를 초기화할 수 있는 클래스는 파생 클래스 뿐이기 때문이다.(항목 4 참조)
- if-then-else 문을 줄줄이 늘어놓는 RTTI 기반의 설계도 피하자.(항목 31 참조)
- 클래스 계통 구조가 바뀔 때마다 유지보수를 해줘야 하는데, 컴파일러는 경고해주지 않는다.
현재 지향적인 사고(present-tense thinking)
- B*가 실제로 D를 가리키는 경우에만 B에 대한 가상 소멸자가 필요하다?
- 아래와 같을 경우에는 B에 가상 소멸자를 둘 필요가 없다는 의미이다.
-
class B { ... }; class D : public B { ... }; B* pb = new D;
- 하지만, 다음과 같은 문장이 추가되면 상황이 바뀐다.
-
delete pb; // 이제는 B에 가상 소멸자가 필요하다.
- 사용자 코드가 약간이라도 바뀌면 정의 코드를 바꿔야 한다는 건 어불성설이다.
- 기본 클래스에 가상 소멸자가 들어 있지 않으면, 파생 클래스나 파생 클래스의 멤버에는 (가상) 소멸자가 들어가선 안된다?
- 다음의 코드는 별 문제가 없지만,
-
class string { public: ~string(); // 소멸자 처리가 필요한 객체 }; class B { ... }; // 가상 소멸자도 필요 없고, 소멸자를 가진 멤버도 필요 없다.
- 아래처럼 B가 파생되어 새로운 클래스가 만들어지면 가상 소멸자가 필요하다는 말이다.
-
class D : public B { string name; // 이제 string 소멸자 처리를 위해 ~B는 가상 소멸자여야 한다. };
- B 사용 방식에 약간의 변화가 생겼을 뿐인데 엄청난 코드 변화가 일어난다.
- 기본 클래스에 애초에 가상 소멸자를 넣어두었다면 이야기할 필요 없는 이슈이다.
- 다중 상속이 이루어진 클래스 계통 구조에 소멸자가 한 곳이라도 끼어 있으면, 모든 기본 클래스에는 가상 소멸자가 들어가야 한다?
- 미래 지향적인 사고 방식상으론, 하위에 소멸자가 하나도 없더라도 기본 클래스는 가상 소멸자가 필요하다.
- 즉 위의 의견들은 아래와 같은 사고 방식이다.
- '지금' 포인터 조작을 어떻게 하고 있는가?
- '지금' 어떤 클래스 멤버에 소멸자가 들어 있는가?
- '지금'은 클래스 계통 구조 안의 어떤 클래스에 소멸자가 들어 있는가?
- 상용 라이브러리 중 어떤 문자열 클래스는 가상 소멸자가 들어있지 않았다.
- 아래는 제작자의 설명.
- String 클래스의 소멸자를 가상 소멸자로 만들지 않은 이유는, vtbl을 추가하고 싶지 않았기 때문입니다. String*을 사용하는 경우를 애초부터 가정하지 않았기 때문에, 이 부분은 별 문제가 아닙니다. 본사는 String*을 썼을 경우에 머리 아픈 문제가 생길 수 있다는 점을 알고 있습니다.
- 가상 함수 테이블이 있느냐 없느냐에 따라 20% 정도의 수행 성능 차이를 보이므로 제작자의 의도가 이해가 가지 않는 것은 아니다.(항목 16 참조)
- 하지만 String 클래스를 사용하는 사용자의 수준은 천차만별이며 어떻게 사용할지 가늠할 수 없다는 문제가 있다.
- 그러므로 C++ 자체 기능으로 파생을 하지 못하게 막았다면 좋았을 것이다.(항목 26 참조)
-
auto_ptr<String> ps(String::makeString("Future tense C++")); // ps를 String 객체의 포인터로 다루지만, // 이제 이 객체의 삭제에 대해 걱정하지 않아도 된다.
- 물론 기존 사용법보단 불편하지만, 위험을 줄일 수 있다면 가치가 있다.
- 현재 지향적인 사고가 틀려먹었다는 뜻은 아니다.
- 지금 지원하는 하드웨어와 플랫폼을 고려하여 구현해야 하기 때문이다.
- 컴파일러가 최신 기능을 구현해줄 때까지 손놓고 있을 수는 없다.
정리 : 미래 지향적인 사고란
- 클래스는 완벽하게 만든다. 어떤 부분은 지금 사용되지 않아도 완벽하게 만든다.
- 인터페이스를 설계할 때에는 자주 사용하는 기능을 모두 포함하고, 자주 일어나는 실수는 막는다.
- 맞게 사용하기는 쉽고, 틀리게 사용하기는 어렵게 만들 것.
- 코드를 일반화하더라도 별 손실이 없는 경우라면, 코드는 일반화한다.
- 예를 들어, 트리 횡단(tree traversal)에 필요한 할고리즘은 방향성 비순환 그래프(directed acyclic graph)에 쓸 수 있도록 일반화한다.
728x90
'개발 > More Effective C++' 카테고리의 다른 글
[More Effective C++] 34. C++, C 혼용하기 (0) | 2024.10.21 |
---|---|
[More Effective C++] 33. 추상 클래스(abstract class) (0) | 2024.10.17 |
[More Effective C++] 31. 다중 디스패치(multiple dispatch) (0) | 2024.10.07 |
[More Effective C++] 30. 프록시 클래스 (0) | 2024.10.04 |
[More Effective C++] 29. 참조 카운팅 (0) | 2024.09.27 |
Comments