최근 근황

오랜만에 글을 쓰는 거 같다..
최근 근황은 뭐 계속 회사 생활의 반복이다.. 눈깜짝하니 입사한지도 1년이 어느새 훌쩍넘었다. 뭔가 해보고 싶은게 많은데, 회사 다니면서 출퇴근 시간도 길고, 내가 체력도 안좋고 부지런한 편도 아니라서 진짜 회사만 다니기 벅찼던 것 같다.
그래도 나름 뭔가 해보려고 최근에 2가지 활동을 했었다.     그중에 하나는 컨트리뷰톤이다. 작년이나 올해 초까지만해도 오픈소스 활동을 했었는데, 회사다니면서 자연스레 멈추게 되었다. 다시 한번 오픈소스 활동을 시작하기 위한 계기를 삼으려고 컨트리뷰톤이라는 프로그램에 참여하여 uftrace 라는 C/C++ function tracing 툴 프로젝트에서 활동을 했다. 현재는 컨트리뷰톤 활동은 끝났고, 이번 달 말에 폐회식?시상식? 이 있을 예정이다.     또 하나는 블록체인 스터디이다. 반년전쯤부터인가 블록체인쪽에 관심이 생겨서 한번 공부를 해보려고 스터디를 하게 되었다. 스터디는 현재 진행형이다.
회사를 다니면서 이렇게 2가지 활동을 동시에 했다. 이 활동들에 내가 투자하려고 했던 시간/에너지 양이 대략 있었는데, 막상 해보니 실제로 투자한 시간은 내 예상의 20% 도 안되는 것 같다. 신규 기능개발로 인해 회사 일이 최근에 바쁘기도 했고, 생각보다 내가 시간과 자기관리를 제대로 못했다. 시간투자를 많이 못한게 아쉽긴 한데, 일단 뭐라도 해본 것에 의의를...
아 그리고, 최근에 우리 팀 우리 파트 채용공고를 새로 냈다. 기존의 채용 공고가 너무 모호하고 매력이 떨어진다고 생각해서 내가 건의를 해서 채용공고의 내용을 바꿔봤다.

채용공고 보기/숨기기
모집부분 - C++ 기반 공용 모듈 개선 및 개발 - C++ 기반 엔진 모듈 개선 및 개발 담당업무 - 사내 C++ 공용 라이브러리 개선 및 개발 - C++ 기반 악성코드 탐지/치료 엔진 개선 및 개발 - 코드 품질 및 개발 프로세스 개선 자격요건 - C++ 활용에 자신 있는 분 - 능동적이고 적극적으로 업무를 수행하시는 분 우대요건 …

[Effective C++ 3판] Chapter 1. C++에 왔으면 C++의 법을 따릅시다. (항목 1~4)

앞으로 Chapter별로 포스팅을 할 계획이다.
책의 내용을 완벽하게 정리하려는게 목적은 아니다.
내가 잘 몰랐던 것과 헷갈리는 것을 위주로 해서 개인적인 생각과 궁금한 것에 대한 추가적인 실험/연구 결과를 정리해나갈 것이다.

Chapter 1. C++에 왔으면 C++의 법을 따릅시다.

항목 1 : C++를 언어들의 연합체로 바라보는 안목은 필수.

[ 이것만은 잊지 말자! ]
- C++을 사용한 효과적인 프로그래밍 규칙은 경우에 따라 달라집니다. 그 경우란, 바로 C++의 어떤 부분을 사용하느냐입니다.

항목 2 : #define을 쓰려거든 const, enum, inline을 떠올리자.

#define에게는 유효범위라는 게 없다. 이 점 때문에 외부 라이브러리와 #define이 겹쳐서 고생한 기억이 여러 번 있다. (부들부들..) 그리고 #define은 그냥 단순 치환방식이기 때문에 매우 불안하다. 따라서 상수를 정의할 때는 const 변수를 사용하는 것이 좋다. 혹은 정수 타입에 해당하는 상수를 위해서 나열자 둔갑술 (enum hack)을 사용하는 방법도 있다. 나열자 둔갑술은 단순히 enum { ... }; 이런식으로 선언함으로서 해당 namespace에 한해서 정수형 상수를 정의할 수 있는 방법이다.

[ 정리해 두고 싶은 것 & 추가적인 사항 ]
- 정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 '정의'가 없어도 된다.

[ 이것만은 잊지 말자! ]
- 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각합시다.
- 함수처럼 쓰이는 매크로를 만들려면 #define 매크로보다 인라인 함수를 우선 생각합시다.

항목 3 : 낌새만 보이면 const를 들이대 보자!

[ 정리해 두고 싶은 것 & 추가적인 사항 ]
// 컴파일러는 비트수준 상수성만 보장해준다.
// 우리는 논리적 상수성을 고려해서 프로그래밍해야한다.
class Example {
private:
    int *data;
    int num;
    mutable int callCnt;
public:
    int* getData() const {
        // It is valid in bitwise constness, but invalid in logical constness.
        return data;    // compile ok.
    }
    int& getData(int idx) const {
        // It is valid in bitwise constness, but invalid in logical constness.
        return data[idx];    // compile ok.
    }
    int& getNum() const {
        // It is both invalid in bitwise constness and logical constness.
        return num;    // compile error.
    }
    void call() const {
        // It is invalid in bitwise constness, but valid in logical constness.
        ++callCnt;    // compile ok.
    }
};
// 상수 멤버와 비상수 멤버 함수이 똑같을 경우엔 
// 비상수버전이 상수버전을 호출함으로서 코드 중복을 피할 수 있다.
class Example {
private:
    Data *data;
public:
    const Data& operator[](int idx) const {
        return data[idx];
    }
    Data& operator[](int idx) {
        return 
            const_cast<Data&>(
                static_cast<const Example&>(*this)[idx]
            );
    }
};
[ 이것만은 잊지 말자! ]
- const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다.
- 컴파일러 쪽에서는 비트수준 상수성을 지켜야 하지만, 우리는 논리적 상수성을 사용해서 프로그래밍해야한다. (mutable keyword)
- 상수 멤버와 비상수 멤버 함수이 똑같을 경우엔 비상수버전이 상수버전을 호출함으로서 코드 중복을 피할 수 있다.

항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자.

객체를 사용하기 전에 그 객체는 반드시 초기화하는 것이 좋다.

[ 정리해 두고 싶은 것 & 추가적인 사항 ]
- 객체 멤버 변수는 선언된 순서대로 초기화 된다. (초기화 리스트에서의 순서는 상관X)
- 생성자 본문에서 초기화를 하면 그건 그냥 이미 초기화된 변수에 대입을 하는 셈. (생성자 본문이 실행되기전에 초기화는 완료된다.) 그래서 반드시 초기화 리스트를 사용해야 하는 것.
POD type이 아닌 지역 정적 객체는 객체 정의에 최초로 닿았을 때 초기화 된다. 
- 위 사진에서 처럼 지역 정적 객체의 생성은 runtime시 생성되었는지 여부가 check된다. 따라서 함수 호출시 마다 assembly instruction 3개 정도의 overload가 추가된다. 

#include <iostream>

class B;

class A {
public:
    unsigned int checksum;
private:
    B& b;
    A(B& b)
        :b(b), checksum(0x12341234)
    {}
public:
    static A& A::GetObject();
};

class B {
public:
    unsigned int checksum;
private:
    A& a;
    B(A& a)
        :a(a), checksum(a.checksum)
    {}
public:
    static B& GetObject() {
        static B b(A::GetObject());
        return b;
    }
};

A& A::GetObject() {
    static A a(B::GetObject());
    return a;
}

int main()
{    
    std::cout << "A : " << std::hex << A::GetObject().checksum << std::endl;
    std::cout << "B : " << std::hex << B::GetObject().checksum << std::endl;

    return 0;
}

소스 : https://gist.github.com/taeguk/1c23e5e00c51f7ad36213081cd094438
그리고 저런식으로 컴파일러가 코드를 생성하기 때문에 때문에 circular dependency가 발생하더라도 무한루프에 빠지지 않고 이상현상을 발생시키게 된다. (물론 특정 컴파일러는 무한루프를 발생시킬 수 도 있다. 컴파일러마다 제각기 다를 것이다.)
또, 멀티쓰레드 환경에서는 지역 정적 객체가 여러개 생성되는 문제가 발생할 수 있다.

[ 이것만은 잊지 말자! ]
- 기본제공 타입의 객체는 직접 손으로 초기화합니다. 경우에 따라 저절로 되기도 하고 안되기도 하기 때문입니다.
- 생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용합시다. 그리고 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열합시다.
- 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 합니다. (순서를 예측할 수 없다. 즉 dependency문제) 비지역 정적 객체를 지역 정적 객체로 바꾸면 됩니다.



이것만은 잊지 말자! 는 책에 각 항목 마지막에 있는 것을 거의 그대로 따왔다.
정리해 두고 싶은 것 & 추가적인 사항 는 책의 내용중 특별히 정리해두고 싶은 것과 내가 추가적으로 알아내고 연구해본 것들을 적었다.
근데 어투가 너무 딱딱한듯...ㅠㅜ 다음에는 chapter 2로 만나요~~

댓글

이 블로그의 인기 게시물

개발다운 개발에 대해...

최근 근황