C++17을 적용하여 수정한 Loki 라이브러리
Loki<library> 다운로드 링크:
http://loki-lib.sourceforge.net/index.php?n=Main.Download
※ C++17을 적용한 Loki 다운받기:
loki-cpp17-added-version.zip
※ Loki 라이브러리 라이선스:
MIT License
Modern C++ Design
의 Loki 라이브러리를 C++17(및 그 하위 버전)에서 추가된 기능을 사용하여 약간 수정을 했습니다.
크게 수정된 부분은 Typelist를 쉽게 생성할 수 있는 가변인자 템플릿을 적용한 클래스 템플릿과, using 선언을 통한 별칭 템플릿,
그리고 std::shared_mutex와 std::atomic을 사용하도록 변경된 Threads.h 헤더입니다. 나머지 수정된 파일들은 앞서 언급된
변경 사항들과 호환이 되도록 수정한 것이 전부입니다.
변경된 파일들에 대해 유닛 테스트를 수행하긴 했으나, 미처 걸러지지 못한 버그가 있을 수 있습니다.
또한 Threads.h 헤더를 포함하는 기존 코드와 호환되지 않을 수 있습니다.
또한 실무 경험이 부족한 상태에서 짠 코드라 여러가지 미숙한 부분이 있을 수 있습니다. 이메일(ajw9105@hanmail.net)을 통해
개선해야 할 점이나 여러 의견들을 받고 있으니 관심 있으신 분들은 메일 보내주셨으면 좋겠습니다.
기반이 되는 소스 코드는
https://sourceforge.net/projects/loki-lib/files/Source%20Code/Modern%20C%2B%2B%20Design/
의 소스 코드 중 Reference 폴더 내의 소스 코드 입니다.
LOKI_USE_CPP17 이라는 값을 갖지 않는 매크로 상수를 정의한 후에, Loki 라이브러리의 헤더파일을 include하면 사용할 수 있습니다.
지금부터 변경된 헤더 파일 및 추가된 .cpp파일에 대해서 설명하겠습니다. 여기서 설명하지 않은 Loki 라이브러리의 사용법은
Modern C++ Design
를 참고하세요.
차례
-
-
이 헤더 파일에서는, 표준에서 빠진 std::auto_ptr 대신 std::unique_ptr을 사용하도록 수정되었습니다.
추가로, 수정된 Threads.h 파일과 호환되도록 스레딩 단위전략 관련 부분이 수정되었습니다.
기본적인 사용법은 기존의 Loki 라이브러리와 동일합니다.
이 외의 개선 사항이 있을 수 있으나, 최신 C++에는 이미 std::function 클래스 템플릿이 존재하므로
다른 부분은 손대지 않았습니다.
-
이 헤더 파일들은 수정된 Threads.h 파일과 호환되도록 스레딩 단위전략 관련 부분만이 수정되었습니다.
기본적인 사용법은 기존의 Loki 라이브러리와 동일합니다.
-
기존의 트릭 대신 static_assert를 사용하도록 수정되어 좀 더 보기 쉬운 컴파일 오류 메시지가 출력될 것입니다.
사용법은 기존의 Loki와 동일합니다.
-
이 헤더 파일이 가장 많은 변경사항이 있는 헤더 파일입니다. 수정된 점을 하나씩 짚어 봅시다.
-
우선 use_read_lock 이라는 비타입 템플릿 매개변수가 하나 추가되었습니다.
따라서 새로운 스레딩 단위전략 클래스 템플릿은 다음과 같이 선언됩니다.
스레딩 단위전략 클래스 템플릿의 선언 예제 코드 보기
- template <class Host, bool use_read_lock = true>
- class SingleThreaded;
-
- template <class Host, bool use_read_lock = true>
- class ObjectLevelLockable;
-
- template <class Host, bool use_read_lock = true>
- class ClassLevelLockable;
-
스레딩 단위전략 클래스 템플릿의 선언 예제 코드 접기
비타입 매개변수 use_read_lock이 true이고 ReadLock(조금 있다 설명합니다)을 사용하여 뮤텍스를 잠근 경우,
std::shared_mutex::lock_shared / std::shared_mutex::unlock_shared 멤버 함수를 통해
잠금/해제를 하여 공유 자원을 읽기만 하는 스레드가 여렷일 경우에 성능상 이점이 생기게 됩니다.
만일 ReadLock을 사용하여 뮤텍스를 잠궜더라도 use_read_lock이 false라면,
std::shared_mutex::lock / std::shared_mutex::unlock
멤버 함수를 사용하여 일반적인 잠금/해제를 수행하게 됩니다.
-
VolatileType, AtomicType 별칭 템플릿이 추가되었습니다. 이 별칭 템플릿으로 스레딩 단위전략 클래스 템플릿과
임의 타입 T(단, AtomicType의 경우 std::atomic의 템플릿 인자로 넘겨줄 수 있는 타입이어야 함)를 넘겨주면,
넘겨받은 스레딩 단위전략 클래스 템플릿에 따라 적절한 타입을 정해줍니다.
VolatileType, AtomicType 예제 코드 보기
- using Result0 = VolatileType<SingleThreaded, int>;
-
- using Result1 = VolatileType<ObjectLevelLockable, int>;
-
- using Result2 = VolatileType<ClassLevelLockable, int>;
-
-
- using Result3 = AtomicType<SingleThreaded, int>;
-
- using Result4 = AtomicType<ObjectLevelLockable, int>;
-
- using Result5 = AtomicType<ClassLevelLockable, int>;
-
VolatileType, AtomicType 예제 코드 접기
-
SafeLock 클래스 템플릿이 추가되었습니다. 이 클래스 템플릿은 세 종류의 특수화가 존재합니다.
첫 번째 특수화 버전은 템플릿 인자로 스레딩 단위전략 클래스 타입 하나를 받습니다.
그리고 생성자에서, 템플릿 인자로 받은 스레딩 단위전략 클래스를 상속받은 Host 클래스의 객체 하나를 받아
그 객체의 std::shared_mutex를 잠금/해제 합니다.
만일 템플릿 인자로 받은 스레딩 단위전략 클래스 타입이 상수 타입인 경우에는,
SafeLock의 생성자로 받을 수 있는 Host 클래스의 객체는 반드시 상수가 되어야 합니다.
이 때, use_read_lock이 true인 경우에는 잠금/해제 시
std::shared_mutex::lock_shared / std::shared_mutex::unlock_shared
멤버 함수가 사용됩니다.
SafeLock 예제 코드 보기
SafeLock의 두 번째 특수화 버전은 ClassLevelLockable 단위전략 클래스 템플릿 사용 시
static 함수에서 SafeLock을 사용하는 경우(SafeLock에 넘겨줄 객체가 없는 경우)에
사용할 수 있도록 만들어졌습니다. 이 특수화 버전은 템플릿 인수로 NullType을 받고, 생성자에 직접
사용할 shared_mutex와 읽기 잠금 사용 여부를 넘겨줘야 합니다.
그러나 ClassLevelLockable의 std::shared_mutex는 private 영역에 숨겨져 있으므로,
여러분이 직접 이 특수화 버전을 사용할 일은 거의 없을 것입니다. 대신 ClassLevelLockable의 public 영역에
선언된 StaticWriteLock과 StaticReadLock의 내부에서 사용되었으므로, 이 두 타입을 사용하여 간접적으로 사용할 수 있습니다.
한 가지 주의할 점은 StaticReadLock의 생성자는 객체를 받지 않으므로, 상수와 읽기 잠금에 대한 컴파일 오류를
받을 수 없다는 것입니다.
StaticLock 예제 코드 보기
-
- template <template <class, bool> class ThreadingModel>
- class Host : public ThreadingModel<Host, true>
- {
- public:
-
-
-
- using ThreadingModel::StaticWriteLock;
- using ThreadingModel::StaticReadLock;
-
-
-
- static void FunctionWrite() {
-
-
- StaticWriteLock lock;
-
- ...
-
- }
-
-
-
-
- static void FunctionRead() {
-
-
- StaticReadLock lock;
-
-
-
-
-
-
-
-
- ...
-
- }
-
-
- ...
- };
-
-
-
-
-
-
- template <template <class, bool> class ThreadingModel>
- class Host : public ThreadingModel<Host, false>
- {
- public:
-
- ...
-
- static void FunctionRead() {
- StaticReadLock lock;
-
-
-
-
- ...
-
- }
-
- ...
- };
StaticLock 예제 코드 접기
SafeLock의 마지막 특수화 버전은 첫 번째 특수화 타입의 목록(list) 버전입니다. 즉, 이 특수화 버전은 템플릿 인자로 스레딩 단위전략 클래스 타입들이 담긴
Typelist 하나를 받습니다. 그리고 생성자에서, 템플릿 인자로 받았던 스레딩 단위전략 클래스들을 각각 상속받은 Host 클래스들의 객체를 하나씩 받아
그 객체들의 std::shared_mutex를 잠금/해제 합니다. 이 때 std::shared_mutex의 주소(포인터)순으로 정렬하여 잠금/해제를 수행하므로,
데드락 걱정 없이 스레딩 단위전략을 사용하는 객체 여렷을 한꺼번에 잠금/해제 할 수 있습니다.
첫 번째 특수화 버전과 마찬가지로, 템플릿 인자로 받은 스레딩 단위전략 클래스 타입이 상수(const) 타입이었다면 그 스레딩 단위전략 클래스를 상속받은 Host 클래스의
객체(SafeLock의 생성자로 넘겨줄)도 상수여야 합니다. 그렇지 않다면 컴파일 오류를 발생시키는 것도 같습니다.
SafeLock 예제 코드 보기
- template <template <class, bool> class ThreadingModel>
- class Host1 : public ThreadingModel<Host1, true>
- {
- public:
-
-
-
- using ThreadingModel::ThisThreadingModel;
-
- ...
- };
-
- template <template <class, bool> class ThreadingModel>
- class Host2 : public ThreadingModel<Host2, true>
- {
- public:
- using ThreadingModel::ThisThreadingModel;
-
- ...
- };
-
- using TM1 = Host1::ThisThreadingModel;
- using TM2 = Host2::ThisThreadingModel;
-
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<TM1, const TM2>>;
-
- ...
-
- Host1 host1;
- const Host2 host2;
-
- {
-
-
-
-
- WriteReadLock lock(host1, host2);
-
-
-
-
-
- ...
-
- }
-
SafeLock 예제 코드 접기
-
자주 사용될 만한 SafeLock의 인스턴스 선언이, 각 스레딩 단위전략 클래스 템플릿의 public 영역에 추가되었습니다.
예제 코드 보기
-
- template <class Host, bool use_read_lock>
- class SingleThreaded
- {
- ...
-
- public:
-
- ...
-
- using ThisThreadingModel = SingleThreaded;
-
-
- struct StaticLock final : private Private::Uncopyable
- {
- StaticLock() noexcept = default;
- explicit StaticLock(const SingleThreaded&) noexcept {}
- };
-
- using StaticWriteLock = StaticLock;
- using StaticReadLock = StaticLock;
- using Lock = StaticLock;
-
- using WriteLock = SafeLock<SingleThreaded>;
- using ReadLock = SafeLock<const SingleThreaded>;
-
-
- using WriteWriteLock = SafeLock<TL::MakeTypelist_t<SingleThreaded, SingleThreaded>>;
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<SingleThreaded, const SingleThreaded>>;
- using ReadReadLock = SafeLock<TL::MakeTypelist_t<const SingleThreaded, const SingleThreaded>>;
-
- ...
-
- };
-
-
- template <class Host, bool use_read_lock>
- class ObjectLevelLockable
- {
- ...
-
- public:
- using ThisThreadingModel = ObjectLevelLockable;
-
-
-
- using WriteLock = SafeLock<ObjectLevelLockable>;
- using ReadLock = SafeLock<const ObjectLevelLockable>;
- using Lock = WriteLock;
-
- using WriteWriteLock = SafeLock<TL::MakeTypelist_t<ObjectLevelLockable, ObjectLevelLockable>>;
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<ObjectLevelLockable, const ObjectLevelLockable>>;
- using ReadReadLock = SafeLock<TL::MakeTypelist_t<const ObjectLevelLockable, const ObjectLevelLockable>>;
-
- ...
-
- };
-
-
- template <class Host, bool use_read_lock>
- class ClassLevelLockable
- {
- ...
-
- public:
- using ThisThreadingModel = ClassLevelLockable;
-
- template <bool read_lock>
- class StaticLock final : private Private::Uncopyable
- {
- public:
- StaticLock() noexcept : lock_(*ClassLevelLockable::mtx(), read_lock && use_read_lock) {}
- explicit StaticLock(const ClassLevelLockable&) noexcept : StaticLock() {}
-
- private:
- SafeLock<NullType> lock_;
- };
-
- using StaticWriteLock = StaticLock<false>;
- using StaticReadLock = StaticLock<true>;
- using Lock = StaticWriteLock;
-
- using WriteLock = SafeLock<ClassLevelLockable>;
- using ReadLock = SafeLock<const ClassLevelLockable>;
-
- using WriteWriteLock = SafeLock<TL::MakeTypelist_t<ClassLevelLockable, ClassLevelLockable>>;
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<ClassLevelLockable, const ClassLevelLockable>>;
- using ReadReadLock = SafeLock<TL::MakeTypelist_t<const ClassLevelLockable, const ClassLevelLockable>>;
-
- ...
-
- };
-
-
-
-
- template <template <class, bool> class ThreadingModel>
- class Host : public ThreadingModel<Host, true>
- {
- ...
-
- public:
-
-
-
- static void StaticReadAndWrite(...) {
- StaticWriteLock lock;
-
- ...
- }
-
-
-
- static void StaticReadOnly(...) {
- StaticReadLock lock;
-
- ...
- }
-
-
- void NonConstMemFn(...) {
- WriteLock lock(*this);
-
- ...
- }
-
-
-
-
-
-
-
- void ConstMemFn(...) const {
- ReadLock lock(*this);
-
- ...
- }
-
-
-
-
- Host& operator =(const Host& rhs) & {
- WriteReadLock lock(*this, rhs);
-
- ...
- }
-
- ...
-
- };
-
-
-
-
-
- template <template <class, bool> class ThreadingModel>
- class Host1 : public ThreadingModel<Host1, true>
- { ... };
-
- template <template <class, bool> class ThreadingModel>
- class Host2 : public ThreadingModel<Host2, true>
- {
- ...
-
- public:
-
-
- Host2& operator =(const Host2& rhs) & {
- WriteReadLock lock(*this, rhs);
-
- ...
- }
-
-
- Host2& operator =(const Host1& rhs) & {
- WriteReadLock lock(*this, rhs);
-
- ...
- }
-
-
- Host2& operator =(const Host1& rhs) & {
- using TM2 = Host2::ThisThreadingModel;
- using TM1 = Host1::ThisThreadingModel;
- using WriteReadLockForDiffType = SafeLock<TL::MakeTypelist_t<TM2, const TM1>>;
-
- WriteReadLockForDiffType lock(*this, rhs);
-
- ...
- }
- };
예제 코드 접기
-
마지막으로 원자적 정수 연산이 std::atomic<int>를 사용하도록 변경되었습니다.
std::atomic<int>는 복사 연산이 금지되어 있으므로, 원자적 연산 함수의 반환 타입과
매개변수 타입들이 값에 의한 전달에서 참조자(혹은 상수 참조자)에 의한 전달로 바뀌었습니다.
-
MakeTypelist가 가변인자 템플릿을 사용하여 템플릿 인자의 개수에 제한이 없도록 수정되었습니다.
또한 MakeTypelist를 비롯한 여러 템플릿들의 별칭 템플릿이 추가되었습니다.
MakeTypelist와 별칭 템플릿 예제 코드 보기
-
-
- using MyTypelist = typename MakeTypelist<T1, T2, T3, ...>::Result
-
-
-
- using MyTypelist = MakeTypelist_t<T1, T2, T3, ...>
MakeTypelist와 별칭 템플릿 예제 코드 접기
다음은 새로 추가된 별칭 템플릿의 목록입니다. 순서는 헤더에 선언된 순서를 따릅니다.
-
MakeTypelist_t
-
TypeAt_t
-
TypeAtNonStrict_t
-
Append_t
-
Erase_t
-
EraseAll_t
-
NoDuplicates_t
-
Replace_t
-
ReplaceAll_t
-
Reverse_t
-
MostDerived_t
-
DerivedToFront_t
별칭 템플릿에 대한 자세한 설명은
Effective Modern C++의
항목 9를 참고하세요.
-
Select 템플릿에 대한 별칭 템플릿 Select_t가 추가되었습니다.
별칭 템플릿을 사용하는 예제 코드는 Typelist.h 헤더 항목 에서 찾으실 수 있습니다.
그리고, 별칭 템플릿에 대한 자세한 설명은
Effective Modern C++의
항목 9를 참고하세요.
-
-
Threads.h 헤더에 추가된 SafeLock 클래스 템플릿과 관련된 코드에서, 코드 부풀림을 최소화 하기 위해 중복되는 코드를
함수로 만들어 cpp파일에 따로 넣었습니다. 직접 이 파일에 접근해야 할 일은 거의 없을 것이므로, 크게 신경쓰지 않아도 되는 부분입니다.