Generic Container Idioms

개념

일반화 컨테이너를 설계할 때 저장되는 타입이 가져야 하는 요구조건을 최소화하는 기술.

실현

Vector<Point> vec(10); 대신 Vector<Point> vec(vec, 10); 방식으로 사용하게 설계한다. 이렇게 하면 복사 생성자를 이용해 원소를 만들게 하므로 T 타입에 기본 생성자가 없어도 된다. 이를 위해 메모리 할당과 생성자 호출을 분리하여 구현한다.

예시

  #include <iostream>
#include "Point.h"

template<typename T>
class Vector {
    T* buff;
    std::size_t size;
    std::size_t capacity;
public:
    Vector(std::size_t sz, const T& value = T() ) : size(sz), capacity(sz) {
        buff = static_cast<Point*>(operator new(sizeof(Point) * sz));
        int i = 0;
        try {
            for (i = 0; i < sz; i++) {
                new(&buff[i]) T(value);
            }
        } catch(...) {
            for (int j = i - 1; j >= 0; --j)
                buff[j].~T();
            operator delete(buff);
            size = 0;
            capacity = 0;
            throw;
        }
    }
    ~Vector() {
        for (int j = size - 1; j >= 0; --j)
            buff[j].~T();
        operator delete(buff);
    }
};
  

Allocator

상황

메모리 할당 해지 방법을 바꾸고 싶다.

std::allocator

메모리 할당 관련 함수를 추상화한 도구.

  • allocate
  • construct
  • destroy
  • deallocate

예시

  std::allocator<Point> ax;
Point* p2 = ax.allocate(1);
ax.construct(p2, 0, 0);
ax.destroy(p2);
  

allocator_traits

construct, destroy의 기본 구현 제공. 사용자 함수가 있으면 그걸 사용한다.

예시

  std::allocator_traits<MyAlloc<Point>>::construct(ax, p1, 0, 0);
  

Policy Based Design

개념

클래스가 사용하는 정책을 템플릿 인자로 전달받아 교체 가능하게 만드는 디자인.

예시

  template<typename T, typename Alloc = std::allocator<T>>
class Vector {
    T* buff;
    std::size_t size;
    std::size_t capacity;
    Alloc ax;
public:
    Vector(std::size_t sz, const T& value = T() ) : size(sz), capacity(sz) {
        buff = std::allocator_traits<Alloc>::allocate(ax, sizeof(T) * sz);
        int i = 0;
        try {
            for (i = 0; i < sz; i++)
                std::allocator_traits<Alloc>::construct(ax, &buff[i], value);
        } catch(...) {
            for (int j = i - 1; j >= 0; --j)
                std::allocator_traits<Alloc>::destroy(ax, &buff[j]);
            std::allocator_traits<Alloc>::deallocate(ax, buff, capacity);
            size = 0;
            capacity = 0;
            throw;
        }
    }
    ~Vector() {
        for (int j = size - 1; j >= 0; --j)
            std::allocator_traits<Alloc>::destroy(ax, &buff[j]);
        std::allocator_traits<Alloc>::deallocate(ax, buff, capacity);
    }
};
  

Rebind

상황

현재 allocator는 bool 단위로 할당하는데 int 단위로 할당하고 싶다.

기존에는 allocator 내부에 rebind 구조체를 정의했음. rebind는 template type을 바꾼 allocator type을 꺼내주는 역할. C++11 이후 allocator_traits로 대체됨. C++20부터는 allocator 안에서 rebind 자체가 삭제됨.

구현

  #include <iostream>

template<typename T>
class MyAlloc {
public:
    using value_type = T; // allocator_traits
};

template<typename T>
void foo(T ax) {
    typename T::template rebind<int>::other ax1; // C++98
    std::cout << typeid(ax1).name() << std::endl;

    typename std::allocator_traits<T>::template rebind_alloc<int> ax2; // C++11~
    std::cout << typeid(ax2).name() << std::endl;
}

int main() {
    std::allocator<bool> ax;
    foo(ax);
}
  

Temporary Proxy

개념

임시 객체를 사용해서 **대행자(proxy)**를 만드는 기술.

예시

  template<typename Alloc>
class Vector<bool, Alloc> {
    struct BitProxy {
        int* buff;
        int idx;
        BitProxy(int* buff, int idx) : buff(buff), idx(idx) {}

        BitProxy& operator=(bool value) {
            // 비트 단위 할당 로직
            return *this;
        }

        operator bool() {
            // 비트값 반환 로직
            return true;
        }
    };

    BitProxy operator[](int idx) { return BitProxy(buff, idx); }
};

int main() {
    Vector<bool> v2(100, false);
    v2[0] = true;      // v2.buff 0번째 비트 설정
    bool b = v2[0];    // BitProxy.operator bool()
}
  

vector<bool>의 경우 참조를 반환하지 못하므로 임시 객체로 proxy를 사용한다. 따라서 vector<bool>int& ref = v[0]; 같은 코드는 사용할 수 없다.