본문 바로가기

C++

[C++]CppCoreGuideLine(3) - Interfaces

- 원문 출처 : https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#S-interfaces

-------------------------------------------------------------------------------------------------------------------------------------

Interfaces

인터페이스는 프로그램의 두 부분이 맺고있는 계약이다. 계약에는 한 서비스의 공급자와 사용자가 기대하는 것을 정확하게 기술하는 것이 필수적이다. 좋은 인터페이스란 서로간의 계약이 잘 맺어진 인터페이스다. 좋은 인터페이스는 이해하기 쉽고, 효율적인 사용을 장려하고, 오류 발생 가능성이 낮춘다. 좋은 인터페이스를 가지는 것은 코드 조직에 가장 중요한 요소일 것이다.

I.1 : 인터페이스를 명확하게 설정해라.

인터페이스에 명시되어 있지 않은 가정은 쉽게 간과될 수 있고, 테스트 하기 힘들어 진다.

나쁜 예시

함수의 동작을 글로벌 변수를 이용해 컨트롤 하는 것은 암시적이고, 혼란을 줄 수 있다.
1
2
3
4
int round(double d)
{
    return (round_up) ? ceil(d) : d;    // don't: "invisible" dependency
}
cs


I.2 : const 가 아닌 글로벌 변수는 피해라.

const가 아니는 글로벌 변수는 독립성을 해치고, 종속성은 예측 불가능한 변화를 일으킨다.


I.3 : 싱글톤 객체는 피해라.

싱글톤 객체는 글로벌 객체이다. 


I.4 : 인터페이스를 정확하고 명확한 타입으로 전달해라.

타입은 가장 간단하고 좋은 문서이다. 타입이 잘 선언된 경우 가독성을 향상시키고, 컴파일 시간에 확인할 수 있다. 또한 정밀하게 입력된 코드는 더 최적화가 잘되어 있는 경우가 많다.

1
2
3
4
5
6
7
8
9
10
11
12
// Bad Example
draw_rect(100200100500); // what do the numbers specify?
draw_rect(p.x, p.y, 1020); // what units are 10 and 20 in?
 
============================================
 
// Better
void draw_rectangle(Point top_left, Point bottom_right);
void draw_rectangle(Point top_left, Size height_width);
 
draw_rectangle(p, Point{1020});  // two corners
draw_rectangle(p, Size{1020});   // one corner and a (height, width) pair
cs


I.5 : (있다면) 전제조건을 명시해라

함수로 전달되는 파라미터는 함수에서 특정 조건을 만족해야만 의미가 있는 경우가 존재한다.

I.6 : 전제조건을 명시할 때 Expects() 사용을 권장한다.

Expects() 함수는 GSL 라이브러리로 사용할 수 있다.

I.7 : 사후 조건을 명시해라.

원하는 결과가 나오는지 체크하고, 혹시 올바른 결과가 나오지 않는다면 구현이 잘못되었는지 확인할 수 있도록 해야 한다.

I.8 : 사후 조건을 명시할 때 Ensure() 사용을 권장한다.

역주)

Expects와 Ensure는 C++20 이후에서 표준으로 정의 될 예정이다.

https://en.cppreference.com/w/cpp/language/attributes/contract


I.9 : 인터페이스가 템플릿이면, 해당 파라미터를 concepts 을 이용해 기록해라.


I.10 : 요구하는 작업 수행에 실패했음을 나타내기 위한 에러를 사용해라.


I.11 : 레퍼런스(T&)나 로우 포인터(T*)로 소유권을 이동시키지 마라.

객체의 소유권이 함수를 호출한 객체가 가지고 있는지, 함수를 가진 객체가 가지고 있는지 확실하지 않다면 메모리 릭이 발생하거나 댕글링 포인터가 발생할 것이다.

예시

1
2
3
4
5
6
X* compute(args)    // don't
{
    X* res = new X{};
    // ...
    return res;
}
cs

위의 예시에서 반환된 X는 누가 지워야 할까? compute 함수가 레퍼런스로 반환하면 더 골치아파 질것이다. 그냥 value 값으로 넘기는게 더 좋을 수 있다. (결과 반환값이 크다면 move 함수를 사용해라)


I.12 : null이 되어서는 안되는 포인터는 not_null을 이용해서 선언해라.

not_null 은 guidelines support library에 선언되어 있다.


I.13 : 배열을 단일 포인터로 전달하지 마라.

(포인터, 사이즈) 스타일의 인터페이스는 개발자가 실수하기 쉽다. 또한 배열을 가르키는 포인터는 함수를 부르는 객체가 배열의 사이즈를 결정해야 하기 때문에 특정 컨벤션에 의존적일 수 밖에 없다.

1
2
3
4
void draw(Shape* p, int n);  // poor interface; poor code
Circle arr[10];
// ...
draw(arr, 10);
cs


I.22 : 글로벌 객체의 복잡한 초기화는 피해라.


I.23 : 함수 인자는 되도록 적게 해라.

인자가 많아질수록 복잡도는 높아진다. 넘겨줘야 하는 인자가 많아진다면 구조화 해서 넘겨주는 인자를 줄이는 것이 좋다.


I.24 : 동일한 타입의 관계 없는 변수들은 인접하지 않도록 해라.

동일한 타입의 인접한 변수는 바꿔서 넣기 쉽다.

1
2
3
4
5
// Example. Bad
void copy_n(T* p, T* q, int n);  // copy from [p:p + n) to [q:q + n)
 
// Better..
void copy_n(const T* p, T* q, int n);  // copy from [p:p + n) to [q:q + n)
cs

예외사항

인자 순서에 상관이 없다면 문제 없다.

1
2
int max(int a, int b);

cs


I.25 : 클래스 계층구조를 위해서는 함수를 추상 클래스로 만드는 것을 선호해라.

추상 클래스가 기본 클래스에 구현되어 있는것 보다 안정적일 가능성이 높다


I.26 : ABI 크로스 컴파일을 원하면 C스타일을 이용해라.

각 컴파일러는 클래스, 예외 처리, 기능 이름 및 기타 구현 세부사항에 대해 서로 다른 레이아웃을 구성한다.


I.27 : 라이브러리 ABI 안정성을 고려해야 한다면 Pimpl 이론을 사용해라.

private 데이터 변수는 클래스 레이아웃에 참여하고, 멤버 함수는 오버로드 확인되기 때문에 구현 세부정를 변경하려면 이 기능을 사용하는 클래스의 모든 부분을 다시 컴파일해야 한다. Pimpl 기법은 클래스의 변경에도 컴파일하지 않아도 되도록 분리할 수 있다.

예시

인터페이스 (widget.h)

1
2
3
4
5
6
7
8
9
10
11
12
class widget {
    class impl;
    std::unique_ptr<impl> pimpl;
public:
    void draw(); // public API that will be forwarded to the implementation
    widget(int); // defined in the implementation file
    ~widget();   // defined in the implementation file, where impl is a complete type
    widget(widget&&= default;
    widget(const widget&= delete;
    widget& operator=(widget&&); // defined in the implementation file
    widget& operator=(const widget&= delete;
};
cs

구현(widget.cpp)

1
2
3
4
5
6
7
8
9
10
class widget::impl {
    int n; // private data
public:
    void draw(const widget& w) { /* ... */ }
    impl(int n) : n(n) {}
};
void widget::draw() { pimpl->draw(*this); }
widget::widget(int n) : pimpl{std::make_unique<impl>(n)} {}
widget::~widget() = default;
widget& widget::operator=(widget&&= default;
cs


(역주) ABI란 

- 참조 : https://stackoverflow.com/questions/2171177/what-is-an-application-binary-interface-abi

(역주) 함수 오버로드 확인(Overload Resolution)

- https://en.cppreference.com/w/cpp/language/overload_resolution


I.30 : 규칙을 깨야할 상황이 생기면 캡슐화해라.


'C++' 카테고리의 다른 글

[C++]CppCoreGuideLine(2) - Philosophy  (0) 2019.01.26
[C++]CppCoreGuideLine(1) - Abstract  (0) 2019.01.24
[C++] std chrono  (0) 2019.01.19
[C++] Smart Pointer  (0) 2019.01.05