VS2017 과 VS2019 프로젝트

둘의 관계는 참으로 이상하다.

둘다 설치한 상태에서 VS2017로 만든 프로젝트는 VS2019로 열리고
VS2019로 만든 프로젝트는 VS2017로 열린다.

VS2017이었을 VS2019로 열리던 프로젝트는 VS2019로 마이그레이션한 이후 VS2017로 열린다!

아동 관련 문제들..

최근 정인이 사건도 그렇고 아이 키우며 그런 사건들을 볼때마다 너무 마음이 아프다.

포탈에 광고로 떠 있던 초록어린이재단의 사연들… 아버지는 지병으로 숨지고 옆에 아사했던 아이… 영하 18도에 밖에서 떨고있던 아이… 학대 받다가 도망친 아이… 방치 당한 아이…

그런 기사들을 의식적으로 안보려고 하다가 보게될때 하나하나 가슴에 박히고 마음을 아프게 한다. 그리고 꽤 오래 잊지못하게 된다.

다 큰 성인 이라도 가슴아픈 슬픈 사연이 도처에 있지만… 어린 아이들의 일에 더 마음이 가는건 내 아이와 겹치지 때문일 것 같다. 자신의 상황을 스스로 해결 할 수 없는 작은 꼬맹이들이 그저 울수밖에 없는 상황을 생각하면 너무 마음이 아프다.

좀 더 좋은 사회가 되어서 이런일이 안생기고 내 아이에게도 그런 일이 안생겼으면 좋겠다. 혹시라도 내 아이에게 그런 일이 생기면 누군가 도와줄수 있는 사회가 되길 기원한다.

젤다의 전설 브래스 오브 더 와일드

2020년 마지막날 젤다 야생의 숨결의 보스를 클리어 했다.
몇년 만에 클리어 하는 게임인지 기억이 가물가물할 정도..
안가본 지역도 많고, 퀘스트들도 많이 있긴하지만 정말 재미있게 했다.

재미있는 게임을 하나 끝내면 여운이 많이 남는다.
오랜만에 즐거운 여운을 느낀듯 하다.

추가로 게임개발자로서 젤다를 만든 사람들은 어떻게 협업을 했고, 디렉터들은 최초 구성은 어땠고, 일은 어떻게 분배하고 이런것도 궁금하다.

소멸자가 호출되는 시점에 지정한 function을 호출하는 클래스

#include <iostream>
#include <functional>

class AutoCloser
{
public:
	using NoParamFunc = std::function<void()>;

	AutoCloser() = delete;

	AutoCloser(NoParamFunc&& func)
	{
		_func = std::move(func);
		_iswork = true;
	}

	~AutoCloser()
	{
		callOnce();
	}

	void call()
	{
		_func();
	}

	void callOnce()
	{
		if (_iswork == true)
		{
			call();
			reset();
		}
	}

	void reset()
	{
		_iswork = false;
	}

private:
	NoParamFunc _func;
	bool _iswork;
};

class Test
{
public:
	~Test()
	{
		std::cout << "Test::~Test()" << std::endl;
	}
};

int main()
{
	Test* value = new Test;

	AutoCloser a([value]() 
	{
		delete value;
	});

	// do something

}
// delete value

std에 비슷한거 없나..

unique_ptr 테스트

간단해 보이지만 써보면 뭔가 쓰기 어려웠다. 대충보고 쓰니 그런것 같다. 그래서 한번 혼자 이렇게 저렇게 써봤다.

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Item
{
public:
    Item()
    {
        cout << "Item constructor : " << this << endl;
    }

    virtual ~Item()
    {
        cout << "Item destructor : " << this << endl;
    }

    virtual void Print()
    {
        cout << "Item : " << this << endl;
    }
};

class Apple : public Item
{
public:
    Apple(int value)
        : _value(value)
    {
        cout << "Apple constructor : " << this << ", value : " << _value << endl;
    }

    virtual ~Apple()
    {
        cout << "Apple destructor : " << this << ", value : " << _value << endl;
    }

    virtual void Print()
    {
        cout << "Apple : " << this <<", value : "<<_value<< endl;
    }

private:
    int _value{0};
};

class Inventory
{
public:
    void SetItem(unique_ptr<Item> item)
    {
        _item = std::move(item);
    }

public:
    unique_ptr<Item> _item;
};

int main()
{
    unique_ptr<Item> uPtrItem(new Apple(1));    // 기본적으로 생성하는 방법은 이렇다. 
    //unique_ptr<Item> uPtrItem2 = uPtrItem;    // 복사생성자는 삭제됨. 소유권이전은 std::move를 쓴다.
    unique_ptr<Item> uPtrItem2 = move(uPtrItem);

    uPtrItem2->Print();
    uPtrItem2.get()->Print();   // raw pointer는 get으로 접근한다.
    //uPtrItem2.release();  // release은 소유권을 포기한다. 메모리 해제는 않함.
    //uPtrItem2.reset(nullptr);  // reset은 소유한 포인터를 교체한다.
    //uPtrItem2 = move(unique_ptr<Item>(nullptr));// nullptr uniqur_ptr을 넣어서 해제할 수도 있다.
    //uPtrItem2 = move(nullptr);    // 그냥 nullptr을 넣어도 된다
    uPtrItem2 = nullptr;  // 어잉 그냥 nullptr을 넣어도 되네?! (코드를 보니 nullptr_t 대입연산자는 reset을 호출하게 구현)

    cout << "===========" << endl;

    Item* rawItem1 = new Apple(10);
    unique_ptr<Item> uPtrItem3(rawItem1);    // 돌아다니는 rawPointer는 이렇게 잡아다가 생성한다.
    
    Item* rawItem2 = new Apple(11);
    unique_ptr<Item> uPtrItem4;
    //uPtrItem4 = rawItem2;   // 자동 변환 되지 않는다.
    uPtrItem4 = unique_ptr<Item>(rawItem2);   // 미리 생성해둔 unique_ptr에 값을 넣을때는 1.unique_ptr을 생성해서 넣어준다.
    //uPtrItem4.reset(rawItem2);  // 미리 생성해둔 unique_ptr에 값을 넣을때는 2. reset을 호출한다.
    uPtrItem4.reset();

    cout << "===========" << endl;

    Inventory inven;
    //inven.SetItem(new Item());    // 자주 실수한 부분. 매개변수로 넘길때는 raw point 가 unique_ptr 로 자동 변환 되지 않는다.

    inven.SetItem(unique_ptr<Item>(new Item));  // unique_ptr 생성자에 raw pointer 를 넣어서 생성하고 인자로 넘어감
    inven.SetItem(unique_ptr<Apple>(new Apple(20)));    // 하위 클래스도 잘 처리됨
    inven.SetItem(unique_ptr<Item>());  // nullptr 을 넣을수 있고, nullptr을 넣을 경우 소멸자에서 문제없음.
    inven.SetItem(unique_ptr<Item>(nullptr));  // unique_ptr을 초기화 시키고 싶다면,
                                                // 클래스 내부에서 reset를 호출하거나 이렇게 nullptr로 다시 세팅해줄수 있음.
    inven.SetItem(nullptr);   // 어잉 이렇게 해도 되네?! (코드를 보니 nullptr_t 대입연산자는 reset을 호출하게 구현)

    cout << "===========" << endl;

    inven.SetItem(make_unique<Apple>(21));  // make_unique 는 생성자에서 필요한 인자를 받아 직접 생성해서 인자로 넘어감
    inven.SetItem(make_unique<Item>());  // 그래서 make_unique 의미상 nullptr을 넣을순 없음
    inven.SetItem(nullptr);

    cout << "===========" << endl;
}

특별할것 없는 일상

두달간 와이프가 오전 근무만 하고 있어서 아침에 리하를 등원시키는 것이 아침 일과가 되었다. 나름 익숙해 진듯 싶으면서도 등원을 시키고 출근을 할때는 나름 피곤하다.

유튜브를 틀어주고 갈아입힐 옷을 옆에 두고 리하의 머리를 빗기면서 문득 이런 일상이 참 행복한일 이구나 하는 생각이 들었다. 그리고 평범한듯 하지만 여러가지 불운이 피해하고 여러가지 행운이 있었기에 이렇게 있을수 있구나 라는 생각이 든다.

언젠가 이때를 떠올리고 그리워 할 것 같다

Visual Studio 2019 C++ one line function format

아래와 같은 코드가 있을때..

template<typename Ty>
class GlobalAutoSingleton
{
public:
    static Ty& Get() { return instance
};

return instance 뒤에 ; 를 붙이는 순간

template<typename Ty>
class GlobalAutoSingleton
{
public:
    static Ty& Get() {
        return instance;
};

이렇게 변경이 되는데.. 도대체 저 서식 옵션이 어디에 있는거냐… 몇일을 찾아도 찾을수가 없다.

Windows의 MultiByteToWideChar/WideCharToMultiByte 용법과 wchar_t, CP_ACP의 의미

MultiByteToWideChar/WideCharToMultiByte 함수는 여기저기서 많이 쓰던 함수이다. MSDN을 보고 사용 코드를 짜보기도 하고 (이 블로그 옛날 글에도 있다.) 나중에는 귀찮으니 인터넷에 있는거 후려다 쓰기도 했다. 용법은 인터넷에 많이 있으니 패스하고 해당 함수를 사용하면서 언제나 헷갈리는 점이 있는데, 파라메터중 코드페이지 이다.

결론 부터 말하면 두 함수의 첫번째 인자인 코드페이지의 의미가 서로 다르다.
MultiByteToWideChar는 변환하려는 멀티바이트의 코드페이지를 의미한다. 즉 코드페이지에 해당하는 멀티바이트가 들어가서 와이트캐릭터로 나온다.
WideCharToMultiByte는 결과로 나오는 멀티바이트의 코드페이지를 의미한다. 즉 와이드캐릭터가 들어가서 코드페이지에 해당하는 멀티바이트로 나온다.

와이드캐릭터의 입력이나 출력에 코드페이지나 인코딩 형태를 입력하지 않는것을 보면 Windows의 와이드캐릭터는 코드 페이지가 존재하지 않는다는 의미이다. 2바이트 캐릭터라고 해도 인코딩 방식은 여러가지 일텐데 왜 코드페이지가 없는가? C++에서 wchar_t는 2바이트 캐릭터타입이라는 의미이지 특정 인코딩이나 코드페이지를 의미하는것이 아니다.

Windows의 관련 인터넷을 뒤져보니 UTF16 고정인듯 한다. (BE, LE중 뭔지도 좀 구분해서 써주지)
1. https://docs.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings
2. https://docs.microsoft.com/ko-kr/cpp/standard-library/filesystem?view=vs-2019

그리고 위 함수의 인자인 코드페이지 가장 많이 사용하는 값인 CP_ACP.
일반적으로 asni 라고도 하는데, 실제로는 실행되고 있는 윈도우(OS)의 디폴트 코드페이지를 의미한다. 즉 국가별로 CP_ACP는 다른 코드페이지 이다.

그래서 중국 윈도우에서 실행된 클라이언트에서 입력한 멀티바이트 문자열을 한국(OS) 서버에서 MultiByteToWideChar 함수에 CP_ACP로 지정해서 변환하면 깨진다.

오랜 기간 C++을 했지만 문자열 인코딩에 대해서 생각이란걸 해보게 된건 최근이다. 그 전에는 이렇게 해보고 ‘아 안되네?’ 다른 값 넣어보고 ‘음 되네’. OS가 바뀌면 되던게 안되기도 하고 그랬다.

약간 다른 문제이지만… 요즘엔 회사에서 Perforce를 쓰는데 소스 코드든 설정 파일이든 그냥 넣은대로 나왔으면 좋겠는데, 내려받을때 다시 인코딩을 변환해서 골때린다.