{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[C++] 스터디 CPPALOM 16주차: Effective C++ Item 46~53

파워 포인트 파일(.pptx)을 Markdown으로 변환하여 업로드하였음)

# <br>CPPALOM

# <br>16주차 – Effective C++ item 46~53
한수빈

# <br>항목46: 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자

* 템플릿 코드는 암시적 타입 변환을 고려해 정적 코드를 생성하지 않는다.
  * 다음 코드는 컴파일 에러를 일으킨다!
  * int형 2가 Rational 자료형으로 암시적 변환될 수 없기 때문이다.

```C++
template<typename T>
class Rational
{
public:
    Rational(const T& numerator = 0,
        const T& denominator = 1);
    const T numerator() const;
    const T denominator() const;
};
template<typename T>
const Rational<T> operator*(
    const Rational<T>& lhs,
    const Rational<T>& rhs);
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2;
```

* 대신, 클래스 템플릿 안에 프렌드를 이용해서 선언하면, 프렌드 함수의 선언은 템플릿의 인자추론에 영향을 받지 않는다.
  * 선언 자체는 일반적인 함수가 되는 것과 마찬가지이다!
* 하지만 이는 링크가 되지 않는다.
  * 클래스 자체는 선언되었으며, friend 멤버 함수는 non-template 함수로 컴파일된다.
  * 하지만 링커는 non-template 함수를 찾을 수 없다. template 함수만 존재하기 때문이다!

```C++
template<typename T>
class Rational
{
public:
    friend const Rational operator*(
        const Rational& lhs,
        const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(
    const Rational<T>& lhs,
    const Rational<T>& rhs)
    {}
```

* [https://isocpp.org/wiki/faq/templates#template-friends](https://isocpp.org/wiki/faq/templates#template-friends)
* 따라서 두 가지 해결책이 있다.
  * 첫째, 함수 바디를 클래스 선언 안에서 정의한다.
  * 둘째, 클래스와 함수를 전방 선언 후 클래스 내 선언에 <>를 추가한다.

```C++
template<typename T>
class Rational
{
public:
    friend const Rational operator*(
        const Rational& lhs,
        const Rational& rhs)
    {
        return Rational( lhs.numerator() * rhs.numerator(),
            lhs.denominator() * rhs.denominator());
    }
};
```

```C++
template<typename T> class Rational;
template<typename T> const Rational operator*(
        const Rational& lhs,
        const Rational& rhs);
template<typename T>
class Rational
{
public:
    friend const Rational operator* <>(
        const Rational& lhs,
        const Rational& rhs)
};
```

# <br>항목47: 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자

* iterator들을 일정 방향으로 이동시키는 advance() 함수가 존재한다.
  * 이는 효율을 위해 iterator의 종류에 따라서 각자 적절한 구현을 가져야 한다.
* iterator 클래스의 특성 정보를 이용하면 되지만, 두 가지 문제가 존재한다.
  * 위 방식의 템플릿 함수가 양방향 반복자에 대해서 생성될 경우, 컴파일 에러가 난다. iter += d가 논리적으로 불가능하기 때문이다. (if문에 의해서 실행이 되지 않음에도)
  * 컴파일 시기가 아닌 런타임 시기에 행위가 결정된다. 이는 효율적이지 못하다.

```C++
template<typename IterT, typename DistT.
void advance(IterT& iter, DistT d)
{
    if (iter is random_access_iterator)
        iter += d;
    else
        if (d >= 0)
            while (d--)
                ++iter;
        else
            while (d++)
                --iter;
}
```

오버로딩을 이용해서 이를 영리하게 해결할 수 있다.

```C++
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
    std::random_access_iterator_tag)
{
    ...
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
    std::bidirectional_iterator_tag)
{
    ...
}
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
    doAdvance(
        iter, d,
        typename std::iterator_traits<IterT>::iterator_category()
    );
}
```

# <br>항목48: 템플릿 메타프로그래밍, 하지 않겠는가?

* 템플릿 메타프로그래밍이란 컴파일 도중에 실행되는 템플릿 기반의 프로그램을 작성하는 것이다.
  * C++ 컴파일러 역시 규칙에 의해서 작동하는 튜링 머신이다.
  * 따라서, 템플릿을 통해 “정적인 코드 결과”를 생성하는 프로그램을 프로그래밍할 수 있다!
* TMP는 컴파일 시기에 실행되는 프로그램이므로, 런타임에 오버헤드가 없다.
  * 가령, 사전에 계산되어야 하는 것을 컴파일 시기에 미리 계산시킬 수 있다.
* 관심이 있다면 관련 서적을 찾아보자!

# <br>항목49: new 처리자의 동작 원리를 제대로 이해하자

* new를 사용했을 때, 할당할 메모리가 부족하면 어떻게 동작할까?
  * 설정된 new_handler를 호출한다.
  * 이는 전역으로 설정된 것이다!
* set_new_handler() 함수를 제공하며, 이를 통해서 void형 함수를 할당해 객체 생성에 실패했을 때 해당 함수가 호출되도록 할 수 있다.
* 기존에 설정되어 있던 new_handler가 반환된다.

```C++
namespace std
{
    typedef void (*new_handler)();
    new_handler set_new_halnder(new_handler p) throw();
}
```

* 이러한 new 처리자는 다음 중 하나를 꼭 해야 한다.
  * 사용할 수 있는 메모리를 더 많이 확보한다.
    * 가령 사전에 메모리를 확보하였다가 new 처리자가 호출되면 해당 메모리를 소멸시키고 할당해준다.
  * 다른 new 처리자를 설치한다.
    * 현재의 new 처리자가 이를 해결할 수 없다면, 다른 new 처리자를 할당해 위임한다.
  * new 처리자의 설치를 제거한다.
    * new 처리자가 nullptr이라면, 예외를 던지게 된다.
  * 예외를 던진다.
    * bad_alloc 혹은 bad_alloc에서 파생된 타입의 예외를 던진다.
  * 복귀하지 않는다.
    * abort 혹은 exit를 호출한다.

# <br>항목50: new 및 delete를 언제 바꿔야 좋은 소리를 들을지를 파악해 두자

* new와 delet는 다음과 같은 목적을 위해 바꾼다.
* 잘못된 힙 사용을 탐지하기 위해
  * new를 호출할 때, 양쪽 경계에 int 하나씩을 추가로 할당한다.
  * 이 int는 고유한 바이트 패턴을 지닌다. e.g., 0xDEADBEEF
  * delete를 호출했을 때, 해당 바이트 패턴이 여전히 같은지 확인한다.
  * 만약 다르다면, 데이터 오버런이 발생한 것이다.
* 효율을 향상시키기 위해
  * 힙 단편화를 방지한다든가, 다양한 애플리케이션의 요구사항에 맞는 new 및 delete를 작성한다.

* 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해
  * 할당된 메모리 블록의 크기는 어떤 분포를 보이는지?
  * 각각의 사용 기간은 어떤 분포를 보이는지?
  * 메모리가 할당되는 순서가 FIFO인지 LIFO인지?
  * 시간 경과에 따라 사용 패턴이 바뀌는지?
  * 한 번에 쓰이는 동적 할당 메모리의 최대량은 얼마인지?
* 할당 및 해제 속력을 높이기 위해
  * 컴파일러가 제공하는 메모리 관리 루틴이 다중 스레드에 맞게 만들어져 있다면, 단일 스레드에 맞게 만들어 속력 이득을 보게 한다.

* 임의의 관계를 맺고 있는 객체들을 한 군데에 나란히 모아 놓기 위해
  * 특정 자료구조들을 위치 지정 new 및 delet를 이용해 특정 위치에 몰려있게 한다.
  * 캐싱에서 이득을 볼 수 있다.
* 그때그때 원하는 동작을 수행하도록 하기 위해
  * 데이터의 보안 강화를 위해 해제한 메모리 블록에 0을 덮어쓰도록 한다.
  * 공유 메모리를 조작하는 일은 C API로밖에 할 수 없을 때 이를 new 및 delete를 변경하여 해결한다.

# <br>항목51: new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자

* operator new는 다음 요구사항을 지켜야 한다:
  * 반환 값이 제대로 되어 있어야 한다.
  * 가용 메모리가 부족할 경우에는 new 처리자 함수를 호출해야 한다.
  * 크기가 없는 메모리 요청에 대한 대비책을 갖춰야 한다.

```C++
void* operator new(std::size_t size) throw(Std::bad_alloc)
{
    using namespace std;
    if(size == 0)
        size = 1;
    while (true) 
    {
        // size byte를 할당
        // if (할당이 성공했음)
        //     return (할당된 메모리에 대한 포인터);
        // new_hanlder globalHandler = set_new_handler(nullptr);
        // set_new_handler(globalHandler);
        if(globalHandler)
            (*globalHandler)();
        else 
            throw std::bad_alloc();
    }
}
```

* 또한 상속의 문제를 고려해야 한다.
  * 파생 클래스가 new 연산자를 오버로딩하지 않을 경우 부모 클래스의 new가 호출되기 때문이다.
  * 즉, 자식 클래스임에도 불구하고 부모 클래스의 생성자가 호출되게 된다!
  * 따라서 다음과 같이 크기를 검사해야 한다.
* delete의 경우도 유사하다.

```C++
void* Base::operator new(std::size_t size) throw(Std::bad_alloc)
{
    if (size != sizeof(Base))
        return ::operator new(size);
}
```

```C++
void Base::operator delete(void* rawMemory, std::size_t size) noexcept 
{
    if(rawMemory == nullptr)
        return;
    if (size != sizeof(Base)) 
{
        ::operator delete(rawMemory);
        return;
    }
    // rawMemory가 가리키는 메모리를 해제한다.    return;
}
```

# <br>항목52: 위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자

* 위치지정 new 함수는 추가 매개변수를 받는 형태를 의미한다.
* 생성자 호출 중 예외가 발생했을 때, C++ 런타임은 할당된 메모리를 소멸시킬 의무가 있다.
  * 이때 C++ 런타임은 호출된 new와 동일한 짝의 delete 호출한다.
  * 만약 짝이 없다면, 전역 delete를 호출한다.
* 따라서 메모리 누출을 피하기 위해 new와 delete의 짝을 항상 맞춰주어야 한다!

```C++
void* oeperator new(std::size_t, void *pMemory) noexcept;
```

# <br>항목53: 컴파일러 경고를 지나치지 말자

* 컴파일러 경고를 쉽게 지나치지 말자.
  * 컴파일러에서 지원하는 최고 경고 수준에서도 경고 메시지를 내지 않고 컴파일되는 코드를 만드려고 노력하자.
* 컴파일러 경고에 너무 기대지도 말자.
  * 컴파일러마다 경고가 천차만별이다.
  * 다른 컴파일러로 이식하면 수많은 경고가 생길 수도, 없어질 수도 있다.

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기