728x90

1. 함수 템플릿 정의

  • 다양한 형식에 대해 호출될 수 있는 함수적 동작을 제공한다.

 

2. 함수 템플릿 예시

template < typename >
T max( T a, T b )
{
    // b < a 라면 a를 반환하고, 아니라면 b를 반환한다.
    return b < a ? a : b;
}

int main()
{
    int i = 42;
    std::cout << "max( 7, i ): " << ::max( 7, i ) << std::endl;

    double f1 = 3.4;
    double f2 = -6.7;
    std::cout << "max( f1, f2 ): " << ::max( f1, f2 ) << std::endl;

    std::string s1 = "mathematics";
    std::string s2 = "math";
    std::cout << "max( s1, s2 ): " << ::max( s1, s2 ) << std::endl;
}

/*
출력 결과
max( 7, i ): 43
max( f1, f2 ): 3.4
max( s1, s2 ): mathematics
*/
  • typename이라는 키워드는 c++98 표준을 만드는 중 상당히 늦게 도입됐다. 그전에는 형식 파라미터를 키워드 class로 도입해야 했기 때문에 여전히 typename 대신 class를 사용할 수 있다.
  • 일반적으로 템플릿은 어떠한 형식이라도 다룰 수 있는 하나의 실체로 컴파일되지는 않는다.
  • 대신 템플릿이 사용될 때마다 템플릿에서부터 각 형식에 맞는 실체를 만든다.
  • 따라서 max()는 위의 세 가지 형식 int max( int,int), double max(double, double), std::string max(std::string, std::string)에 맞춰 각각 컴파일된다.
  • 템플릿 파라미터를 실제 형식으로 바꾸는 작업을 인스턴스화라고 한다.

 

3. 템플릿 인자 영역

template < typename >
T max( T a, T b );
//...

int main()
{
    int i = 10;
    const int c = 42;
    ::max ( i, c );   // T는 int로 연역된다.
    ::max ( c, c );   // T는 int로 연역된다.

    int& ir = i;
    ::max ( i, ir );    // T는 int로 연역된다.

    int arr[4];
    ::max ( &i, arr );  // T는 int*로 연역된다. 


    /*오류 발생*/
    ::max( 4, 7.2 );    // T는 int로도 double로도 연역될 수 있다.
    
    std::string s;
    ::max( "hello", s );    // T는 char const[6]로도, std::string로도 연역될 수 있다.

    /*해결 방법*/
    //1. 두 인자가 모두 일치하게 인자를 변환한다.
    ::max( static_cast< double >( 4 ), 4.2 );

    //2. 컴파일러가 형식 연역을 시도하지 않게 T의 형식을 명시한다.
    ::max< double >( 4, 4.2 );

    //3. 파라미터가 다른 형식을 가질 수 있게 명시한다. 다중 템플릿 파라미터
}
  • 호출 파라미터를 참조자로 선언하면 사소한 변환도 형식 연역에 적용되지 않는다.
  • 같은 템플릿 파라미터 T도 선언된 두 인자의 형식은 완전히 동일해야 한다.
  • 호출 파라미터를 값으로 선언하면 형 소실에 해당하는 사소한 변환은 지원한다.
  • const나 volatile로 한정한 것은 무시하며, 참조자는 참조된 형식으로 변환하고, 배열이나 함수는 그에 맞는 포인터형으로 바뀐다.
  • 템플릿 파라미터 T로 선언된 두 인자에 대해 형 소실이 일어나고 난 후의 형식이 서로 완전히 같아야 한다.

 

4. 다중 템플릿 파라미터

// 다중 템플릿 파라미터
template < typename T1, typename T2 >
T1 max( T1 a, T2 b )
{
    return b < a ? a : b;
}

auto m = ::max( 4, 5.2 );
  • 다른 형식을 갖는 두 호출 파라미터를 넘길 수 는 있지만, 반환형이 선언돼야만 한다는 문제가 있다.
  • 호출 인자의 순서에 따라 반환형이 바뀌게 된다.

 

5. 반환형 연역

template< typename T1, typename T2 >
auto max( T1 a, T2 b )
{
    return b < a ? : a : b;
}
  • c++14에서 부터 어떠한 특정 반환형도 선언하지 않는 방식으로 ( auto 사용 ) 컴파일러가 반환형을 찾아내게 할 수 있다.

 

6. 기본 템플릿 인자

// 참조자가 반환되지 않도록 std::decay_t<> 사용
template< typename T1, typename T2, typename RT = std::decay_t< decltype( true ? T1() : T2() ) > >
RT max( T1 a, T2 b )
{
    return b < a ? a : b;
}

// 반환값의 기본값을 명시하기 위해 std::common_type<> 사용
template< typename T1, typename T2, typename RT = std::common_type_t< T1, T2 > >
RT max ( T1 a, T2 b )
{
    return b < a ? a : b;
}

// 기본 인자값을 가지도록
template< typename RT = long, typename T1, typename T2 >
RT max ( T1 a, T2 b )
{
    return b <  a ? a : b;
}

 

7. 함수 템플릿 요약

  • 함수 템플릿은 다양한 템플릿 인자에 대한 함수 군을 정의한다.
  • 템플릿 파라미터에 종속된 함수 파라미터에 인자를 전달하면 함수 템플릿은 템플릿 파라미터 형식을 연역한 수 해당 형식으로 인스턴스화 된다.
  • 템플릿 파라미터를 명시적으로 한정 지을 수 있다.
  • 템플릿 파라미터에 대해 기본 인자를 정의할 수 있다. 이전 템플릿 파라미터를 사용할 수도 있고, 그 뒤에 기본 인자를 갖지 않는 파라미터가 나올 수도 있다.
  • 함수 템플릿을 오버로딩할 수 있다.
  • 함수 템플릿을 오버로딩한다면 수정 사항은 템플릿 파라미터를 명시하는 것 정도에 그쳐야 한다.
  • 오버로딩한 함수 템플릿을 호출하기 전에 모든 오버로딩 버전을 '볼' 수 있어야 한다.
반응형
728x90

1. 교착 상태 정의

  • 두 스레드가 서로를 기다리는 상황

2. 교착 상태 예시

#include <iostream>
#include <Windows.h>
#include <thread>
#include <mutex>

class CriticalSection
{
private:
	CRITICAL_SECTION m_critSec;

public:
	// 생성자
	CriticalSection();

	// 소멸자
	~CriticalSection();

	void Lock();
	void UnLock();
};

CriticalSection::CriticalSection()
{
	InitializeCriticalSectionEx( &m_critSec, 0, 0 );
}

CriticalSection::~CriticalSection()
{
	DeleteCriticalSection( &m_critSec );
}

void CriticalSection::Lock()
{
	EnterCriticalSection( &m_critSec );
}

void CriticalSection::UnLock()
{
	LeaveCriticalSection( &m_critSec );
}

class CriticalSectionLock
{
private:
	CriticalSection* m_pCritSec;

public:
	// 생성자
	CriticalSectionLock( CriticalSection& InCritSec );

	// 소멸자
	~CriticalSectionLock();
};

CriticalSectionLock::CriticalSectionLock( CriticalSection& InCritSec )
{
	m_pCritSec = &InCritSec;
	m_pCritSec->Lock();
}

CriticalSectionLock::~CriticalSectionLock()
{
	m_pCritSec->UnLock();
}

int a;
CriticalSection a_mutex;
int b;
CriticalSection b_mutex;

int main()
{
	
	std::thread t1( []()
	{
		while ( true )
		{
			CriticalSectionLock lock1( a_mutex );
			a++;
			CriticalSectionLock lock2( b_mutex );
			b++;
			printf( "thread1 a : %d, b : %d\n", a, b );
		}
	} );

	std::thread t2( []()
	{
		while ( true )
		{
			CriticalSectionLock lock1( b_mutex );
			b++;

			CriticalSectionLock lock2( a_mutex );
			a++;
			printf( "thread2 a : %d, b : %d\n", a, b );
		}
	} );

	t1.join();
	t2.join();
}

  • 두 스레드는 잠금과 해제를 반복하다가 어느 순간에 교착 상태에 빠지게 된다.

 

3. 디버그 -> 창 -> 스레드 확인 시 현재 2개의 스레드 상태 확인

  • 교착 상태가 발생한 스레드 주소 16진수에서 10진수로 변경 시 문제가 되는 스레드 확인이 가능하다
  • 42136 스레드 교착 상태 : ( 16진수 ) 0x0000000000007a8c-> ( 10진수 ) 31372 
  • 31372 스레드 교착 상태 : ( 16진수 ) 0x000000000000a498-> ( 10진수 ) 42136
  • 두 스레드 콜스택 확인 시 서로를 기다리며 교착상태가 발생한 것을 확인할 수 있다.
반응형

'C++ > 게임 서버 프로그래밍' 카테고리의 다른 글

[Game Server] 임계 영역과 뮤텍스  (0) 2022.07.12
728x90

1. 뮤텍스( mutex ) 정의

  • 뮤텍스( mutex )는 상호 배제( mutual exclusion )의 줄임말이며 경쟁 상태를 해결하는 방법 중 하나

2. 사용 방법

int main()
{
	// 뮤텍스 선언
	std::mutex mutex1;

	// 뮤텍스 락
	mutex1.lock();      

	// 함수 사용
	func1();     
	
	// 뮤텍스 잠금 해제 
	mutex1.unlock();     
}
  • C++ 표준 객체 std::mutex 사용 예시

 

int main()
{
	// 뮤텍스 선언
	std::recursive_mutex mutex2;

	// 뮤텍스 락
	std::lock_guard< std::recursive_mutex > lock( mutex2 );

	// 함수 사용
	func1();
}
  • std::mutex 사용 시 예외 발생으로 인해 unlock() 함수 실행이 안될 수 있다.
  • std::recursive_mutex는 로컬 변수가 파괴될 때, 파괴자 함수에서 unlock() 함수가 자동으로 실행되도록 함.

 

[참고]

https://en.cppreference.com/w/cpp/thread/recursive_mutex

반응형

'C++ > 게임 서버 프로그래밍' 카테고리의 다른 글

[GameServer] 교착 상태 디버깅  (0) 2022.07.13
728x90

1. std::source_location을 이용한 로그 작성

  • 기존 __FILE__, __LINE__ 매크로를 사용한 로그 방식을 개선하고자 추가된 내용이다.

 

2. 사용 방법

void log( std::string_view InMsg, const std::source_location& InLocation = std::source_location::current() )
{
    std::cout << InMsg << InLocation.file_name() << " " << InLocation.line() << std::endl;
}

int main()
{
    // C++ 20에 추가된 std::source_location을 이용한 로그 작성
    log( "Error : " );

    // 기존 __FILE__, __LINE__ 매크로를 이용한 로그 작성
    std::cout << "Error : " << __FILE__ << " " << __LINE__ << std::endl;
}
  • 매크로 방식에서, 호출 지점의 정보를 담은 객체를 사용한 방식으로 개선되었다.
  • std::source_location 클래스는 소스 파일 이름, 행 번호, 열 번호, 함수 이름과 같은 정보를 제공한다.

 

3. std::source_location::current() 정의

_STD_BEGIN
struct source_location {
    _NODISCARD static consteval source_location current(const uint_least32_t _Line_ = __builtin_LINE(),
        const uint_least32_t _Column_ = __builtin_COLUMN(), const char* const _File_ = __builtin_FILE(),
        const char* const _Function_ = __builtin_FUNCTION()) noexcept {
        source_location _Result;
        _Result._Line     = _Line_;
        _Result._Column   = _Column_;
        _Result._File     = _File_;
        _Result._Function = _Function_;
        return _Result;
    }

    _NODISCARD_CTOR constexpr source_location() noexcept = default;

    _NODISCARD constexpr uint_least32_t line() const noexcept {
        return _Line;
    }
    _NODISCARD constexpr uint_least32_t column() const noexcept {
        return _Column;
    }
    _NODISCARD constexpr const char* file_name() const noexcept {
        return _File;
    }
    _NODISCARD constexpr const char* function_name() const noexcept {
        return _Function;
    }
  ...
  ...
 }
  • std::source_location::current()는 호출 지점의 정보를 담은 객체를 생성한다.

 

[참고]

https://en.cppreference.com/w/cpp/utility/source_location

반응형

'C++ > C++ 20' 카테고리의 다른 글

[C++ 20] std::erase_if 를 이용한 요소 삭제  (0) 2022.02.25
[C++ 20] std::span  (0) 2022.02.25
[C++ 20] 템플릿 람다  (0) 2022.02.23
[C++ 20] consteval, constinit 지정자  (0) 2022.02.23
[C++20] 지명 초기화  (0) 2022.02.22

+ Recent posts