728x90
반응형

C++코딩의정석 15

컨스트럭터 내에서 할당 대신 초기화를 사용하라

한 번 설정하고, 여러 곳에서 활용하자. 컨스트럭터 내에서 멤버 변수의 설정을 위한 할당 대신 초기화를 사용하면 불필요한 런터임 작업과 타이핑을 줄일 수 있다. 컨스트럭터는 내부적으로 초기화 코드를 만들어낸다. 다음 코드를 보자. class A { string s1_, s2_; public: A() { s1_ = "Hello,"; s2_ = "World"; } }; 실제로는 여러분이 다음과 같이 작성한 것처럼 컨스트럭터의 코드가 만들어진다. A() : s1_(), s2_() { s1_ = "Hello,"; s2_ = "World"; } 즉 직접 초기화하지 않은 개체는 표준 컨스트럭터를 통해 자동으로 초기화되고, 할당 연산자를 통해 할당된다는 것이다. 주요한 개체의 할당 연산자는 이미 만들어진 개체를 다룬..

최소화된 클래스를 사용하라

나누고 정복하라. 작은 클래스가 만들기도 쉽고, 얻기도 쉬울 뿐만 아니라 테스트하고 사용하기도 쉬우며, 다양한 상황에서 활용하기에도 편리하다. 다양한 기능을 가진 클래스 대신 간단한 개념을 담은 작은 클래스를 만들어 활용하자. 하나의 클래스에서 복잡하고 완전한 기능을 제공하는 방식은 분명 매력적인 것이 사실이지만, 작은 클래스를 사용하는 것이 대부분의 시스템에 있어 보다 효율적인 이유가 여러 가지 있다. 최소화된 클래스는 각각 하나의 명확한 개념만을 포함하므로, 여러 개의 분리된 개념을 포함하는 클래스와는 달리 서로 간의 구분이 쉽다는 장점이 있다. 작은 클래스는 보다 이해하기 쉽고, 사용하기 쉬우며 재활용하기도 쉽다. 작은 클래스는 분산과 배치에도 유리하다. 많은 기능이 집적된 클래스의 경우 별도의 덩..

내부의 것은 너무 노출시키지 말라.

클래스가 관리하는 내부 테이터에 대한 핸들을 리턴하여 클라이언트가 개체의 상태를 좌우하게끔 만드는 것은 좋지 않다. 다음 코드를 보자. class Socket { public: ................. int GetHandle() const { return handle_; } // 이렇게 해서는 안된다. private: int handle_; }; 데이터를 숨기는 것은 강력한 추상화와 모듈화의 도구이다. 하지만 데이터를 숨겨놓고 그 제어권을 내주는 것은 스스로를 파괴하는 행동이며, 마치 대문을 잠가놓고 열쇠를 꽂아두는 것과 같다. 그 이유는 다음과 같다. 클라이언트가 기능을 제어할 두 가지 방법을 가지게 된다 클래스의 추상체(Socket)를 이용하거나, 클래스가 의존하고 있는 임플리먼테이션을 제어..

만들고 있는 클래스가 무엇인지 확실히 하라

클래스는 종류에 따라 그 쓰임이 다르며, 적용되는 규칙 또한 다르다. 먼저 값 클래스(std::pair, std::vector)는 다음과 같은 특징이 있다. 값을 중심으로 한 공용 디스트럭터, 복사 컨스트럭터, 할당이 존재한다. 가상 함수가 없다. 기반 클래스가 아닌 구체적인 클래스로 사용된다. 다른 클래스의 직접적인 멤머로서 또는 스택 내에서 인스턴스가 만들어진다. 기반 클래스(base calss)는 클래스 계층을 이루는 단위로, 다음과 같은 특징이 있다. 공용(public)이면서 가상(virtual)이거나 보호된(protected)이면서 가상이 아닌 디스트럭터, 비 공용 복사 컨스트럭터, 할당 연사자 등이 있다. 가상 함수를 통해 인터페이스를 구성한다. 인스턴스는 힙에서 동적으로 만들어지거나 스마트 ..

간접적인 타입 변환을 피하기 위해 오버로딩을 활용하라

간접적인 타입 변환은 코딩의 편리함에는 도움을 줄는지 몰라도 임시 개체가 만들어진다는 점에서 그다지 효율적이지는 못하다. 이러한 추가적인 개체의 생성을 피하고 최적화를 이루기 위해서는 변환이 일어나지 않는 범위 내에서 일반적인 인자 타입을 사용한 오버로딩 함수를 사용하면 된다. 사무실에서 일하던 중 종이가 다 떨어졌다면 어떻게 할 것인가? 복사를 담당하는 동료에게 가서 몇 장을 얻어오면 잠시나마 문제는 해결될 것이지만 그렇게 오래 가지는 못할 것이다. 간접 변환이 하는 일이 바로 이것이다. 임시적인 개체를 만들어 잠지 문제를 해결하는 것을 말한다. class string { string(const char* text); // 간접 변환을 가능하도록 함 }; bool operator==(const stri..

표준적인 형식의 산술 및 할당 연산자를 사용하라

a+b가 있다면 a+=b도 같은 의미인 것이 좋다. 즉 두 대상 사이에 이루어지는 산술 연산자의 경우 같은 효과를 가지는 할당 연산자도 제공해야만 중복을 피하고 효율을 극대화할 수 있다. 일반적으로 바이너리 연산자 @가 있다면 a@=b나 a=a@b와 같은 할당 연산 버전도 준비하고, 같은 의미를 가지게끔 해주는 것이 좋다. 이를 위해서는 다음과 같이 @ 연산을 @= 방식으로 만들면 된다. T& T::operator@=(const T&) { // .... 구현 내용 ... return *this; } T operator@(const T& lhs, const T& rhs) { T temp(lhs); return temp @=rhs; } 두 함수의 기능은 같다고 볼 수 있다. 할당 형식은 실제 작업을 수행하고..

정의의 의존성과 순환 의존성을 최소화하라

지나치게 의존적인 것은 좋지 않다. 정의 내용을 #include 하여 의존하게 되는 상황을 줄여야 한다. 상호 의존적인 것도 피해야 한다. 순환 의존성은 두 모듈이 직/간접적으로 서로에게 의존하고 있을 때 발명하는데, 모듈은 기능이 응집된 단위이기 때문에 서로 의존하는 모듈은 진정한 개별 모듈이라 볼 수 없으며, 오히려 하나의 커다란 모듈로 보는 것이 옳다. 즉, 순환 의존성은 모듈의 장점에 위배되는 것으로, 큰 프로젝트에서 피해야 할 부분이다. 타입의 정의가 절실히 필요한 경우가 아니라면 일방적으로 방향으로의 선언을 사용하는 것이 좋다. 클래스 C의 완전한 정의가 필요한 두 가지 경우를 보자. C 개체의 크기를 알아야 할 경우 e.g. 스택에 C를 할당할 때나 다른 타입과 직접적으로 연결된 멤버로 사용..

너무 긴 함수와 많은 중첩 구조는 피하라

짧은 것이 긴 것보다 낫고, 평면적이 것이 깊은 것보다 낫다. 모든 함수는 그 이름에 맞는 적절한 하나의 역할을 가져야 한다. 여러 개의 작은 요소들이 합쳐서 하나의 긴 함수로 만드는 것은 결코 좋은 방법이 아니다. 너무 긴 라인으로 구성된 함수와 블록(if, for, while, try 등)이 중첩된 구조는 함수의 이해를 힘들게 하고, 관리를 힘들게 하는 주범이다. 블록을 중첩해서 사용하면 그만큼 코드를 읽는 사람의 입장에서 이해가 힘든 것이 당연하다. 블록을 읽어 내려가다가 내부에 다른 블록이 등장하면 지금까지 읽었던 내용을 기억해 둔 상태에서 새로운 블록의 내용을 모두 파악하고, 다시 기억해둔 내용을 꺼내어 조합해야 하기 때문이다. 가능하면 함수의 최대 길이를 제한하는 습관을 가지고, 다음과 같은 ..

변수는 항상 초기화하여 사용하라.

깔끔한 상태에서 시작하라. 초기화되지 않은 변수는 C와 C++에서 있어 버그의 온상이므로, 상상 변수 사용 전에 깨끗한 메모리 상태를 만들어야 한다. 순차적 언어인 파스칼, C, 포트란, 코볼 등을 사용할 때는 변수를 사용하는 코드와 정의하는 코드가 분리되고, 사용되기 직전에 값을 할당하지만, 이러한 방식은 절대 권장되지 않는 형태이다. 초기화 되지 않은 변수에 대한 일반적인 오해 중 하나는 그로 인해 프로그램의 실행이 방해받을 것이라는 점이다. 하지만 이러한 프로그램들은 몇 년간 별다른 문제없이 실행될 수 잇다. 다만 이후에 다른 문맥에서의 호출, 재 컴파일 등 프로그램의 다른 부분에 생긴 변화로 인해 간헐적인 프로그램 중단이 발생할 수 있다. 표준 초기값이나 ? 를 사용하는 경우.. // 권장되지 않..

const를 사용하라.

변하지 않는 값은 그만큼 이해하기도 쉽고, 추적하기도 쉽기 때문이다. 따라서 값을 정의할 때 그 이유가 적당하다면 const를 사용하여 상수를 만드는 것이 좋다. 상수는 안전하고, 컴파일 시에 검사 되며, C++타입 시스템과 부합된다는 것을 기억하라. 상수가 많으면 상수가 정의된 부분과 값만을 신경 쓰면 되기 때문에 그만큼 코드가 간결해진다. void Fun(vector&v) { //... const size_t len = v.size(); //... } const는 일종의 바이러스이다. 한 곳에 일단 위치를 잡으면 코드의 다른 부분으로 전파되기 쉬우며, 아직 const로 지정되지 않은 부분과의 함수 호출 교류가 있을 때에는 그 부분을 const로 만들어버리는 상황을 발생시킨다. 하지만 이는 버그가 아닌..

런타임 오류보다는 컴파일이나 링크 타임 오류가 낫다.

빌드 시에 해야 할 일을 런타임까지 미루지 말라. 컴파일러가 컴파일을 진행하면서 잘못된 부분을 찾을 수 있게 하는 것이 런타임에 검사하도록 하는 것보다 좋은 방법이다. 런타임 검사는 컨트롤과 데이터에 의존적이기 때문에, 완벽하게 대비하기 힘들다. 반대로 컴파일 시의 검사는 컨트롤과 데이터에 의존하지 않기 때문에 비교적 완벽한 신뢰를 줄 수 있다. C++ 언어는 오류 검사를 컴파일 타임에 진행되도록 하여 보다 가속화할 수 있는 다양한 기회를 제공한다. 정적인 검사는 데이터와 프로그램 흐름에 독립적이다. 정적인 검사는 프로그램의 입력과 흐름에 관계가 없으므로, 그만큼 검사가 수월하다. 반대로 런타임 검사의 경우는 적절한 신뢰를 위해 다양한 입력 샘플과 환경에서 테스트해 볼 필요가 있다. 정적으로 표현된 모델..

안전한 공유를 위한 코딩의 시기와 방식을 결정하라.

애플리케이션이 다중 스레드나 프로세스를 이용한다면, 공유 개체를 최소화할 수 있는 방법과 올바르고 안전한 공유를 위한 시기를 알아야 한다. C++ 표준에는 스레드에 대한 별다른 언급이 없지만 C++는 멀티스레드 코드를 만드는 데 꾸준하게 이용되고 있다. 만약 여러분의 애플리케이션이 스레드 간의 데이터를 공유하고 있다면 반드시 안전한 방법을 강구해야 한다. 타깃 플랫폼의 문서를 참조하라 아주 사소한 정수 연산에서부터 내부 프로세스 및 크로스 프로세스 연산에 이르기까지 로컬 범위의 싱크로 문제에 대해서는 일단 타깃 플랫폼이 어떠한 기능을 갖추고 있는지부터 확인해야 한다. 플랫폼의 기본 기능들을 여러분의 자체적인 추상체에 포함시켜라 여러 플랫폼 간의 호환이 필요할 때 좋은 방법이다. 플랫폼이 지원하는 기능을 ..

정보를 숨겨라.

추상화되어 있는 엔티티의 내부 정보는 누출하지 않는 것이 좋다. 추상화를 제어하는 호출 코드와 추상화의 임플리먼테이션 사이의 의존성을 최소화하기 위해서는 임플리먼테이션의 내부 데이터를 숨겨야 한다. 그렇지 않으면 호출 코드가 그 정보에 접근(심한 경우는 제어까지)하여 내부 정보가 누설되기 때문이다. 데이터 대신에 추상체(가능하면 도메인 추상화를 사용하고, 최소한 get/set 추상화 정도는 써야 한다)만을 노출하라. 정보를 숨기면 프로젝트의 비용, 스케줄 그리고 위험을 줄일 수 있다. - 변경 내용이 내부에 국한된다 : 정보를 숨기면 변경 내용이 다른 부분에 영향을 주는 것을 줄일 수 있고, 그만큼 그 비용이 절약된다. - 외부의 접근을 방어한다 : 코드의 관리 기능을 제한하여 버그나 외부 침입을 막는 ..

적절한 규모 유지를 위해서는 '언제, 어떻게' 를 아는 것이 중요하다.

폭발적인 데이터 증가를 주의하라. 너무 이른 최적화는 피하되, 복잡함의 정도를 분명하게 지켜보아야 한다. 데이터를 제어하는 알고리즘은 예측이 가능하고, 선형보다 나쁘지 않으며, 데이터를 처리하는 시간이 적절한 것을 택하자. 최적화가 필요한 시점이 되면 작은 추가 부분을 줄이는 미세한 최적화보다는 전체적인 그림의 복잡성을 줄이는 데 노력하자. 메모리와 디스크 용량은 계속해서 눈에 띄게 증가하고 있다. 예를 들어 1988년부터 2004년까지의 디스크 용량의 증가는 연간 112%에 이르고, 무어의 법칙을 적용한다고 해도 59%나 된다. 따라서 분명한 것은 여러분이 지금 만들어 놓은 코드가 이후에도 계속 사용된다면 보다 많은 데이터를 처리하게 될 것이라는 점이다. 잘못된 알고리즘이라면 그 증가량을 버티지 못할 ..

정확성, 간결성, 명확성을 먼저 생각하라.

KISS ( Keep It Simple Software ) : 정확한 것이 빠른 것보다 좋고, 간단한 것이 복잡한 것보다 좋으며, 안전한 것이 불안한 것보다 좋다. 간결한 디자인과 깔끔한 코드의 중요성은 아무리 강조해도 지나침이 없다. 프로그램은 사람이 읽을 수 있도록 쓰여져야 하고, 컴퓨터가 실행할 수 있도록 하는 것은 그 후의 문제다. "헤롤드 아벨슨, 제랄드 제이 수스맨" 프로그램을 쓸 때는 사람을 먼저 생각하고, 컴퓨터는 두 번째로 생각하라. "스티브 맥코넬" 컴퓨터 시스템의 가장 값싸고, 빠르고 안정적인 구성 요소는 바로 "없는" 요소이다. "고든 벨" 빠진 구성 요소야말로 가장 정확한 요소이며, 가장 안전한 요소이며, 디자인하고, 문서화하고, 테스트 관리하기 가장 쉬운 요소이다. 간결한 디자인..

728x90
반응형