[TIL] 24-01-22

Lev·2024년 1월 22일
0

📓TIL Archive

목록 보기
27/33

클래스

delete

/*
자동으로 생성된 복사생성자, 대입연산자 등을 통한
복사가 발생하는 것을 막고 싶다면...

1. 복사생성자와 대입연산자를 private에 두기
- 좋은 방법은 아니다
- 해당 함수 내에서 복사를 시도하기라도 하면 완전히 막을 수도 없다

2. delete
- 함수를 명시적으로 사용하지 않겠다는 뜻
- 복사가 필요한 상황이 되면 그것을 내가 인지하고 원하는 방식으로 복사하기 위함
*/

class Test
{
public:
	void operator=(const Test& _Other) = delete;	// (2)

/*
private:
	void operator=(const Test& _Other)	// (1)
	{

	}
*/
};

int main()
{
	Test NewTest0;
	Test NewTest1;
	// NewTest0 = NewTest1;
}

RValue 복사생성자 대입연산자

/*
RValue를 사용해 복사생성자 또는 대입연산자를 호출하는 경우를 대비하여,
따로 RValue용 복사생성자와 대입연산자를 명시하여 복사 방식을 지정해줄 수 있다.
컴파일 시, 복사생성자 또는 대입연산자에 인자로 들어가는 요소가 RValue인지 여부를
판단하여 적절한 함수를 호출하도록 되어 있다.

&& => RValue를 나타내는 기호 (딱히 레퍼런스의 레퍼런스 같은 문법적 요소는 아니다)
*/

#include <iostream>

class Test
{
public:
	int* NewValue = nullptr;

	Test()
	{
		NewValue = new int();
		// 동적할당이기 때문에 무조건 힙영역에 생성된다
	}

	~Test()
	{
		if (nullptr != NewValue)
		{
			delete NewValue;
			NewValue = nullptr;
		}
	}

	// RValue 복사생성자
	Test(Test&& _Other) noexcept
	{
		if (nullptr != NewValue)
		{
			delete NewValue;
			NewValue = nullptr;
		}

		NewValue = _Other.NewValue;
		_Other.NewValue = nullptr;
	}

	// 일반 복사생성자
	Test(const Test& _Other)	// (2)
	{
		if (nullptr != NewValue)
		{
			delete NewValue;
			NewValue = nullptr;
		}

		NewValue = new int[100];

		for (size_t i = 0; i < 100; i++)
		{
			// 깊은 복사
		}
	}

	// RValue 대입연산자
	void operator=(Test&& _Other) noexcept	// (1), (2)
	{
		if (nullptr != NewValue)
		{
			delete NewValue;
			NewValue = nullptr;
		}

		NewValue = _Other.NewValue;
		_Other.NewValue = nullptr;
	}

	// 일반 대입연산자
	void operator=(const Test& _Other)
	{
		if (nullptr != NewValue)
		{
			delete NewValue;
		}

		NewValue = new int[100];

		for (size_t i = 0; i < 100; i++)
		{
			// 깊은 복사
		}
	}
};

/*
(1) 객체를 함수 내에 생성하는 경우
함수가 return되면 객체는 사라진다.

RValue -> RValue 대입연산자 호출
*/
Test CreateTest()
{
	Test NewTest;

	return NewTest;
}

/*
(2) 객체를 전역에 생성하는 경우
함수가 return되어도 객체가 사라지지 않는다.

일반 복사생성자 호출 -> RValue 만들기(???) -> RValue 대입연산자 호출

Test NewTestG;

Test CreateTest()
{
	return NewTestG;	// return Test(NewTestG);
}
*/

int main()
{
	Test NewTest = Test();

	NewTest = CreateTest();
	/*
	Test mainTest = CreateTest();
	NewTest = mainTest;

	이렇게 두 줄의 과정을 거치는 것과 같다.
	여기서 mainTest는 이름은 없지만 분명히 존재하는, RValue이다.
	이 경우, CreateTest()의 return값은 함수가 종료되면 사라지지만,
	RValue의 생성자에서 동적할당된 NewValue는 여전히 힙영역에 남아있기 때문에
	깊은복사 방식을 채택하는 대신 해당 메모리를 NewTest의 NewValue에 연결해주는
	방식을 RValue 대입연산자에 명시하여 자원낭비를 막을 수 있다.
	*/
}

예외 처리

try, catch, noexcept

#include <iostream>

void Function2() /*throw(int)*/
// 명시해주는 것은 선택이지만, 적어두면 알아보기 편하다.
{
	int* Ptr = nullptr;

	if (Ptr != nullptr)
	{
		throw 0;	// "int 예외"
	}
	if (Ptr != nullptr)
	{
		throw true;	// "bool 예외"
	}
	if (Ptr == nullptr)
	{
		// 바깥쪽에서 catch해주지 않으면 아래의 코드가 실행된다.
		throw "포인터가 nullptr 이었습니다";
	}
}

// [noexcept]
// throw와 catch가 적용되지 않고 에러가 난 장소에서 바로 프로그램이 터진다.
void Function1() throw(int, bool) /*noexcept*/
{
	Function2();
}

void Function()
{
	try
	{
		// [try] 예외처리를 해야하는 코드
		Function1();
	}
	// 예외를 throw하면 그에 맞는 catch 코드가 실행된다.
	catch (int Exp)
	{
		std::cout << "int 예외" << std::endl;
	}
	catch (bool Exp)
	{
		std::cout << "bool 예외" << std::endl;
	}
	/*
	catch (const char* Exp)
	{
		std::cout << Exp << std::endl;
	}
	*/
}
int main()
{
	Function();
}

/*
throw와 if의 차이
- if문은 함수 내부에서만 처리 가능하다.
- 위와 달리 throw는 외부에서 해당 예외사항을 인지할 수 있게 해준다.
- 일반적으로 사용자는 라이브러리(다른 프로젝트)의 내부를 볼 수 없기 때문에,
  외부에 예외를 알리는 것은 의미가 있다.
*/

Visual Studio

템플릿 만들기

/*
자주 사용하는 클래스나 코드의 형태가 있다면 형태를 저장해서 한번에 불러오는 것

- 프로젝트 템플릿
	프로젝트 설정까지 저장하는 것

- 항목 템플릿 (<- 요것만 다룬다)
	특정 항목만 저장해두는 것
	.h와 .cpp은 각각 내보내야 한다 (동시에 내보내는건 안되기 때문)

(Tip!) Visual Studio 자체를 원드라이브에 넣어두면
Microsoft 로그인을 통해 어디서든 같은 환경을 유지할 수 있다.

---------------------------------------------
<만들기>
상단 메뉴 -> 프로젝트 -> 템플릿 내보내기(.h)
-> .\Visual Studio 2022\My Exported Templates
-> 압축파일에서 .h 파일, MyTemplate.vstemplate, __Templateicon.ico 빼내기
-> $rootnamespace$를 전부 $safaitemname$으로 바꾸고 저장

-> 상단 메뉴 -> 프로젝트 -> 템플릿 내보내기(.cpp)
-> .\Visual Studio 2022\My Exported Templates
-> 압축파일에서 .cpp 파일 빼내기
-> $rootnamespace$를 전부 $safeitemname$으로 바꾸고 저장

-> MyTemplate.vstemplate 수정
	<TemplateData>
		<DefaultName>DefaultClass.h</DefaultName>
		<Name>(VS에서 실제로 표기될 이름 작성하기, 가급적 A로 시작하면 상단에 뜨기 때문에 좋다.)</Name>
	</TemplateData>
	<TemplateContent>
		<References />
		<ProjectItem SubType ="" TargetFileName="$fileinputname$.h" ReplaceParameters="true">DefaultClass.h</ProjectItem>
		<ProjectItem SubType ="" TargetFileName="$fileinputname$.cpp" ReplaceParameters="true">DefaultClass.cpp</ProjectItem>
	</TemplateContent>

-> .\Visual Studio 2022\Templates\itemTemplates
-> 새로운 폴더 생성
-> .h 파일, .cpp 파일, MyTemplate.vstemplate, __Templateicon.ico 가져다 넣기
-> Visual Studio 재시작

---------------------------------------------
<불러오기>
새 항목 추가 -> 템플릿 선택 (항목 템플릿)
*/

WinAPI

윈도우도 게임엔진과 유사하다고 생각하자

#include "framework.h"
#include "WindowsProject1.h"

#define MAX_LOADSTRING 100

// 전역 변수 : 
HINSTANCE hInst;                        // 현재 인스턴스
/*
[HINSTANCE]
윈도우는 사용자에게 포인터를 주지 않는다.
대신 관리용 표식을 정수로 제공하고, 이는 주소값 또는 동적할당 포인터나 다름없는 역할을 한다.
프로그램을 켤 때, xx번 프로그램이라고 배정된 번호가 전역으로 저장되어 있다.
*/
WCHAR szTitle[MAX_LOADSTRING];          // 제목 표시줄 텍스트
WCHAR szWindowClass[MAX_LOADSTRING];    // 기본 창 클래스 이름

// 함수 선언 :
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

// 진입점 :
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // 사용하지 않은 인자 사용
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // (TODO)

    // 전역 문자열 초기화
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = 0;
    // HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
    // 게임에서 윈도우 시스템 단축키는 사용하지 않기 때문에 로드하지 않아도 상관 없다.

    MSG msg;

    // 기본 메시지 루프
    while (GetMessage(&msg, nullptr, 0, 0))
    /*
    이 while문은 GetMessage()로 인해 변화? 없이는 진행되지 않기 때문에 중단점이 적용되지 않는다.
    하지만 게임은 Alt+Tab으로 창을 전환해도 계속 메모리를 점유하고 동작하고 있어야 한다.

    GetMessage()
    - 윈도우에 사용자가 변화를 주면 작동하는 동기함수

    일반적인 게임
    - 윈도우에 사용자가 변화를 주는 여부와 상관없이 계속 돌아간다

    따라서 윈도우가 변화를 마냥 기다리는 것이 아니라, 계속 while문을 돌리며 상황에 따라
    적절한 할일을 하도록 해주어야 하는데, 이를 메세지 콜백방식으로 해결할 수 있다. (WndProc())
    */
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

// 창 클래스 등록 : 
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    /*
    typedef struct tagWNDCLASSEXW {
    WNDPROC     lpfnWndProc;
    } WNDCLASSEXW
    typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

    정의로 이동하다 보면, 함수포인터의 형태임을 알 수 있다.
    윈도우는 사용자가 무엇을 하고싶은지 처음부터 알 수 없기 때문에
    사용자가 함수포인터를 통해 실행시키고 싶은 내용을 넣어주면
    윈도우는 특정 조건 하에 그것을 실행시켜준다.
    이는 가상함수테이블을 기반한 다형성과 유사한 원리이다.

    복잡하게 생겨서 그렇지, 아래와 같이 해당 함수 형식에 맞게 작성하기만 하면 작동한다.
    __int64 __stdcall Test(HWND _Hwnd, unsigned int, unsigned __int64, __int64)
    {
        return 0;
    }
    wcex.lpfnWndProc    = Test;
    */
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = nullptr;
    // wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
    // 게임에서 윈도우창 상단 메뉴는 필요 없다.
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    // 윈도우 창 클래스 만들기
    return RegisterClassExW(&wcex);
}

// 인스턴스 핸들 저장, 주 창 만들기 :
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   // 인스턴스 핸들
   hInst = hInstance;

   // 윈도우 창 생성
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   /*
   [HWND]
   새 창을 만들 때에도, xx번 프로그램에서 xxx번 윈도우를 띄운다는 일련번호를 주고
   이는 사용자가 윈도우를 제어하는 핸들로 사용할 수 있다.
   */ 

   if (!hWnd)
   {
      return FALSE;
   }

   // 윈도우 창 띄우기
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

// 주 창 메시지 처리 : 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    // 애플리케이션 메뉴 처리
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    // 주 창 그리기
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // (TODO)
            EndPaint(hWnd, &ps);
        }
        break;
    // 종료 메세지 게시하고 반환
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// 정보 대화 상자 메시지 처리기 :
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == 2)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}
profile
⋆꙳⊹⋰ 𓇼⋆ 𝑻𝑰𝑳 𝑨𝑹𝑪𝑯𝑰𝑽𝑬 ⸝·⸝⋆꙳⊹⋰

0개의 댓글