프로그래밍 일반/C++ Core Guildlines

C++ Core Guidlines 철학

지노윈 2019. 11. 11. 13:21
반응형

아이디어를 직접 코드로 표현해라

첫번째 month 함수는 명확히 Month를 반환하도록 선언되어 있으며, Date 개체의 상태를 변경하지 않을 것처럼 보인다.

두번째 버전은 코드를 읽는 개발자들을 고민하게 만들며, 발견하기 어려운 버그를 유발할 가능성이 있다.

Month month() const;  // do

int month();          // don't

아래의 반복문은 std::find를 이용해 표현 가능하다.

find를 사용하면 의도를 더 명확하게 드러낼 수 있다.

void f(vector<string>& v)
{
    int index = -1;                    // bad

    for (int i = 0; i < v.size(); ++i) {
        if (v[i] == val) {
            index = i;
            break;
        }
    }

    auto p = find(begin(v), end(v), val);  // better
}

의도를 표현하라

아래 코드는 v를 순회하겠다는 의도가 드러나지 않는다. 

gsl::index i = 0;

while (i < v.size()) {
    // ...
}

 

다음은 순회의 의도가 명확히 드러난다.

for (const auto& x : v)

2개의 int 값으로 2차원 좌표를 표현하고 싶다면 다음과 같이 의도를 명확히 해라.

draw_line(int, int, int, int);  // 불명확

draw_line(Point, Point);        // 명확

런타임 검사보다는 컴파일 타임 검사를 선호하라

static_assert를 사용하여 컴파일 타임에 검사합니다.

static_assert(sizeof(Int) >= 4);

이때 span을 사용하기를 권장한다.정적 크기의 배열을 다루는 함수의 인자로 배열의 포인터와 사이즈를 즐겨 사용한다.

void read(int* p, int n);   // read max n integers into *p

int a[100];
read(a, 1000);    // bad, 배열의 크기를 넘었다

 

 코드가 안전해 졌다.

void read(span<int> r); // read into the range of integers r

int a[100];
read(a);        // better: 배열의 크기를 컴파일러가 알게한다.

컴파일 타임에 검사할 수 없다면 런타임에 검사할 수 있어야 한다

다음과 같이 함수에 값을 넘기면 아무것도 런타임에 체크 할 수 없다.

extern void f(int* p);

void g(int n)
{
    // bad: the number of elements is not passed to f()
    f(new int[n]);
}

포인터와 요소의 갯수를 하나의 개체로 합쳐서 전달해야 한다.

이 디자인은 개체의 필수 부분으로 요소의 갯수를 전달하므로 오류가 발생하지 않고 항상 저렴하지는 않지만 런타임 검사를 할 수 있다.

extern void f4(vector<int>&);
extern void f4(span<int>);

void g3(int n)
{
    vector<int> v(n);
    f4(v);                     // pass a reference, retain ownership
    f4(span<int>{v});          // pass a view, retain ownership
}

런타임 오류는 초기에 잡아라

"미스터리"한 크래시를 피한다.

void increment1(int* p, int n)    // bad: error-prone
{
    for (int i = 0; i < n; ++i) ++p[i];
}

void use1(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment1(a, m);   // m은 오타(n이어야 한다), m <= n이 의도이나 m == 20이 가정
    // ...
}

코드를 개선해보자. 이제 m<=n은 호출 시점에서 확인할 수 있다. 

void increment2(span<int> p)
{
    for (int& x : p) ++x;
}

void use2(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment2({a, m});   // m은 오타(n이어야 한다), m <= n이 의도이나 m == 20이 가정
    // ...
}

m은 오타이므로 코드를 더욱 단순히 한면 다음과 같다.

void use3(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment2(a);   // the number of elements of a need not be repeated
    // ...
}

 

리소스가 새도록 하지 마라

Bad 예

void f(char* name)
{
    FILE* input = fopen(name, "r");
    // ...
    if (something) return;   // bad: if something == true, a file handle is leaked
    // ...
    fclose(input);
}

RAII를 사용한 개선

void f(char* name)
{
    ifstream input {name};
    // ...
    if (something) return;   // OK: no leak
    // ...
}
  • RAII (Resource Acquisition Is Initialization)
    자원 획득은 초기화 시점에서 일어나야 하며, 초기화 시점에서 해야 할 중요한 작업은 자원 획득이다.
    C++에서 자주 쓰이는 idiom으로 자원의 안전한 사용을 위해 객체가 쓰이는 스코프를 벗어나면 자원을 해제해주는 기법이다.

시간이나 공간을 낭비하지 마라

이것이 C++이다.

struct X {
    char ch;
    int i;
    string s;
    char ch2;

    X& operator=(const X& a);
    X(const X&);
};

X waste(const char* p)
{
    if (!p) throw Nullptr_error{};
    int n = strlen(p);
    auto buf = new char[n];
    if (!buf) throw Allocation_error{};
    for (int i = 0; i < n; ++i) buf[i] = p[i];
    // ... manipulate buffer ...
    X x;
    x.ch = 'a';
    x.s = string(n);    // give x.s space for *p
    for (gsl::index i = 0; i < x.s.size(); ++i) x.s[i] = buf[i];  // copy buf into x.s
    delete[] buf;
    return x;
}

void driver()
{
    X x = waste("Typical argument");
    // ...
}

코드를 굳이 설명하지 않아도 무엇이 낭비 되고 있는지 알 수 있을 것이다.

지저분한 구조가 코드를 통해 퍼지기 보단, 캠슐화를 하라.

지저분한 코드는 버그를 숨기고 쓰기가 더 어렵다. 좋은 인터페이스는 사용하기 더 쉽고 더 안전하다.

지저분한 저수준의 코드는 이러한 코드를 더 많이 낳고 상황을 더욱 악화 시킨다.

int sz = 100;
int* p = (int*) malloc(sizeof(int) * sz);
int count = 0;

// ...
for (;;) {
    // ... read an int into x, exit loop if end of file is reached ...
    // ... check that x is valid ...
    if (count == sz)
        p = (int*) realloc(p, sizeof(int) * sz * 2);
    p[count++] = x;
    // ...
}

대신 vector를 사용할 수 있다.

vector<int> v;
v.reserve(100);
// ...
for (int x; cin >> x; ) {
    // ... check that x is valid ...
    v.push_back(x);
}

'프로그래밍 일반 > C++ Core Guildlines' 카테고리의 다른 글

C++ Core Guidlines 함수  (0) 2019.11.11
C++ Core Guidlines 인터페이스  (0) 2019.11.11
C++ Core Guidlines 소개  (0) 2019.11.11