주인장 공지입니다. Lu's…〃 Diary。

반갑습니다. 루사인이라고 합니다.


1. 책 리뷰 & 프로그래밍 일기 블로그입니다.

2. 가끔 성과 글도 올라와요.

3. @Lusain_Kim 에 상주 중.



환영합니다.

[C++11] R-Value와 이동 의미론(move semantics) Lu's…〃 Programing。


C++11 되며 일반적으로 값으로 알고 있던 개념을 L-ValueR-Value 분류하였다.

 

L-Value R-Value 다음과 같이 생각하면 된다.

 

int I = 5; 에서,

L-Value

i

이름이 있고 지속되는

R-Value

5

해당 줄에서만 살아있는 임시

 

 추가적으로, int i = make_int( 10 ); 이라는 코드에서 make_int( 10 ) 역시 R-Value이다.

이름을 가지지 않았고, 지속되지 않기 때문이다.

가장 단순한 R-Value 확인 방법은 앞에 주소 연산자(&) 붙여보는 것이다.
  R-Value 지속되지 않기 때문에 L-Value 참조를 없다.

 

 R-Value 지원하게 되자 가지 이득이 생겼다.

 

  1. 임시 값은 L-Value 참조를 없다. 그런데 R-Value 참조가 생기며 문제가 해결됐다.
  2. L-Value 참조 연산이 아닌 R-Value 참조 연산이 추가되었다.
    L-Value
    참조 생성자/할당자는 연산 후에도 매개인자의 값이 남기 때문에 복사 연산이라고 부르지만, R-Value 참조 생성자/할당자는 연산 매개인자의 값이  남지 않기 때문에( 임시 값이기 때문에) 이러한 연산을 이동 연산이라고 한다.
  3. 유일한 소유권(원본의 복사가 불가능하도록) 구현할 있게 되었다.
  4. 이상 사용되지 않을 L-Value 값을 이동 연산을 사용하여 비울 있다.
    • 임시 값이 아니지만, std::move 연산으로 임시 값처럼 만들 있다.
    • R-Value 캐스팅을 해도 값이 이동하지는 않는다. 이동 처리를 직접 작성해야 한다.
    • 이동연산을 수행한 값은 (이동 연산의 구현 목적대로 구현했다면) 비워진 상태이므로 사용하면 된다.

 

 

 

R-Value 사용하는 가장 좋은 예는 STL(StandardTemplate Library)에서 찾을 있다.

  1. STL Container들의 emplace 연산은 Element 이동 생성자를 지원하면 이동 생성을 한다.
    • 대부분의 STL Container std::Container < Element > 표현된다.
    • 이동 생성자를 지원하지 않거나, 이동 생성자에 예외가 없지 않을 경우(noexcept 키워드가 없을 경우) 복사 생성자가 호출된다.
  2. 소유권이 유일해야 하는(복사되면 되는) 객체를 만들 사용할 있다.
    표준 라이브러리 unique_ptr shared_ptr에서 사용한다.
    • shared_ptr 경우에는 shared_ptr 객체 내부에서만 소유권을 공유해야 하므로 외부 객체로 shared_ptr 만들기 위해서는 객체를 이동 연산으로 생성하여 외부 객체를 비워야 한다.
    • 복사 생성/복사 할당 금지는 소유권을 정해 소멸 책임을 확실히 있다.

 

 

 

  외에도 객체의 내부를 건들지 않고 객체를 이동시킬 불필요한 객체의 복제를 시행하지 않게 하여 낭비를 줄일 있게 되었다.

 

 

 

 그렇다면 R-Value 어떻게 참조할 있는가?

L-Value 참조는 &(앰퍼런트) 붙였다.

R-Value L-Value보다늦게 들어왔기 때문에, & 하나 붙는다. 반쯤 농담이지만 정말 붙었다.

 

 

type-id && expression= R-Value;

 

  1. 그럼 R-Value Reference R-Value일까, L-Value일까?

 

 

 

  1. 정답은 L-Value이다. 지속되거든.

 

 그렇기 때문에 R-Value Reference std::move 함수로 R-Value 캐스팅해줘야 한다.

만약 매개인자로 받은 R-Value Reference 이동 외에 사용해야 한다면 이동하기 전에 사용해야 한다.

 


 

 여기까지 정리를 하자면

 

  1. C++11부터 R-Value 개념이 도입되었다.
  2. R-Value 임시 값이다. 함수의 반환 , 원시 상수 이름이 없고 지속되지 않는 의미한다.
  3. L-Value R-Value처럼 std::move라는 함수로 캐스팅할 있다.
    함수 이름이 std::move라고 정말 객체가 이동하지는 않는다.
  4. R-Value 참조를 매개 인자로 받는 생성자/할당자를 만들 있다. 이는 이동 생성자/할당자라 한다.
  1. 이동 연산은 이름대로 값을 이동시키지 않는다. 이동은 직접 구현해야 한다.
  2. 기본 타입(Primitive Type : int, short, float, double, char ) 소멸자도, 어떤 생성자도 없는 구조체의 이동 연산은 복사와 동일하다.
    • 확실하지는 않다. 정확한 레퍼런스를 아시는 분은 알려주시면 감사하겠습니다.

 

소결론.

R-Value 참조를 사용한 이동 연산은 특수한 형태의 복사 연산이다.

 

 다만 경우에 따라 최적화할 있는 방법이 있으면 조금이라도 최적화를 하자는 뜻으로 만드는 것이다.

 

 


참고하면 좋은 곳:

1. MSDN : https://msdn.microsoft.com/ko-kr/library/dd293665.aspx




next▷ universal Reference



[C++17] filesystem Lu's…〃 Programing。


C++17에서, 경로와 디렉터리, 확장자 등에 관련된 연산을 하는 C++ 표준 라이브러리가 추가됐다. boost API 있는 기능이었지만, 사용법은 약간 다른 같다(boost API 적이 없어서 확답을 하겠다).

 

namespace std::experimental::filesystem::v1 정의되어 있으며(#include<experimental/filesystem>), C++ 표준라이브러리와 같이 사용하면 굉장히 편하게 사용이 가능하다.

 

장점 :

  1. 파일 경로를 저장하기 위한 path 클래스를 지원한다.
    path 클래스의 멤버는 문자열과 경로 설정을 위한 기능을 제공한다.
    path 클래스를 사용하여 간단하게 확장자를 확인하고 교체할 있다.
  2. path 클래스에서 UNICODE MULTIBYTE 모두 지원한다. 이것은 std::wstring std::string 같이 문자 집합에 따라 여러 개의 정의가 있는 것이 아니라, 하나의 filesystem 클래스에서 여러 문자 집합으로의 변환을 지원한다는 뜻이다.
    , 문자집합을 변경할 때마다 클래스를 변경할 필요가 없다.
  3. std::wstring, std::string, char, wchar_t  어떤 타입을 인자로 받아도 상관없다.
    2
    번과 이어지는 장점.
  4. 플랫폼 독립적이다.
    • 정확히는 표준을 지원하면 동일한 코드 사용 가능. 표준을 써야하는 이유.
  1. 파일 복사, 디렉토리 생성, 파일/디렉토리 제거, 현재 디렉토리, 임시 디렉토리 등을 확인하는 함수를 제공한다.

 

 

 

이제부터 어떻게 사용하는지 확인해보자.

std::experimental::filesystem::v1 붙이기에는 무리가 있으므로 생략한다.

 

기본적으로 filesystem 정의된 대부분의 함수는 path 클래스를 사용한다.

path 클래스를 사용하는 방법은 매우 간단하다. std::string 동일하게 사용하면 된다.

path 클래스는 문자 배열과 표준 string 클래스를 통한 생성을 지원한다.

 

path myPath = "C:/Lusain/game.txt";

 

여기서 .txt .exe 변경하고 싶으면 다음 멤버함수를 호출한다.

 

myPath.replace_extension(".exe");

 

또는, game 아닌 LusGame으로 변경하고 싶으면 다음 멤버함수를 호출한다.

 

myPath.replace_filename("LusGame.exe");

replace_filename으로 파일명을 바꾸면 확장자까지 바뀌므로 주의.

 

 외부에서 읽은 문자열로 만든 path 클래스가 원하는 확장자를 가졌는지 확인하는 방법은 다음과 같다.

 

if (myPath.extension() == ".txt")// TODO: ~~

 

 

 

리소스를 관리할 , 종류에 따라 여러 디렉토리에 접근해야 한다.
/
연산자가 오버로딩 되어 있기 때문에, 다음과 같이 작성할 있다.

 

// MyData/Image 저장하려고 .

string data {"myImage.png" };

string DirData { "MyData"};

string DirImage {"Image" };

 

path SavePath = DirData / DirImage / data;




 만약 path 클래스의 데이터를 문자열로 가져오고 싶으면 멤버함수를 이용하면 된다.

.c_str()

.generic_string()

.generic_wstring()

.generic_u8string()

.generic_u16string()

.generic_u32string()

.string()

.wstring()

.u8string()

.u16string()

.u32string()

 

여기서 generic_ 붙은 멤버함수는 백슬래쉬(\) 경로 구분자를 슬래쉬(/) 바꿔 반환한다.

 


 

filesystem 파일 또는 디렉토리의 생성, 삭제, 복사, 이름 바꾸기, 이동, 존재 여부 등을 전역 함수로 지원한다. 자세한 것은 MSDN 참고.

 

예시를 들어보자.

파일인지 확인

is_regular_file(path)

디렉토리인지 확인

is_directory(path)

존재하는지 확인

exists(path)

파일명 변경

rename(path, path)

파일 삭제

remove(path)

파일 또는 디렉토리 복사

copy_file(path, path, copy_options)

디렉토리 생성

create_directory(path)

 

 

 

path 디렉토리라면, 이를 인자로 directory_iterator 만들어 해당 디렉토리의 파일을 순회할 있다. 이를 통해 특정 디렉토리의 파일을 모두 얻어오는 함수를 만들 있다. 맨 마지막 인자로 하위 디렉토리를 재귀적으로 추가할 것인지 설정할 수 있다.

 

bool GetFiles(vector<path>& v, const path& Dir, bool bSearchSubDirectories = true)

{

if ( ! is_directory(Dir) ) return false;

for (const auto& entry : directory_iterator { Dir} )

{

if ( is_regular_file( entry.status() ) )

v.push_back( entry.path() );

else if( is_directory( entry.status() ) )

GetFiles(v, entry.path(), bSearchSubDirectories);

}

return true;

}

 

더욱 자세한 것은 MSDN 참고하자. Modern C++부터는 MSDN에서 설명해준다!


Github 학생 개발자 팩을 얻어보자! Lu's…〃 Programing。




github는 가장 유명한 무료 온라인 git 저장소 제공 사이트이다. 

가입을 하기만 해도 무료 공개 레포지토리 생성이 가능하고, 매달 일정 금액을 지불하면 무제한으로 비공개 레포지토리 생성이 가능하다.

학생의 경우, 학생이라는 것만 인증하면 Github Education에서 학생용 개발자 팩이 제공된다.

 이 팩은 매달 일정 금액을 내는 것과 동일하게 무제한 비공개 레포지토리 생성이 가능한 패키지이다. 기한은 2년이고, 2년 뒤에도 학생이라면 갱신이 가능하다.

긴 말 하지 않고, 과정을 전부 스크린샷으로 찍었다. 하나씩 따라가자.




1. https://education.github.com/ 에 접속하여 [Get the pack]을 누른다.




2.  [Get your pack]을 누른다.




3.자신의 Github 계정에 접속한다.




4. [Yes, I'm a student] 버튼을 클릭한다.




5. 정보를 입력한다. 
단, [select your school-issued email address] 항목에서 선택 가능한 학교 계정이 자신의 Github 계정에 등록되어 있어야 한다.




6. 없다면? 등록하러 가자.
http://github.com/settings/emails 에 접속하여 Add email address 에 학교 계정을 입력한다.




7. 학교 계정에서 유효한 계정인지 확인한다.
verify email address 하이퍼링크를 누르면 된다.




8. 정상적으로 등록했다면 5번으로 돌아가 나머지 정보를 입력하고(다시 한 번 말하지만, 학교 계정을 선택해야 한다) 제출하면

끝났다. 당신은 학생 개발자 팩을 얻었다. 이제 마음껏 비공개 레포지토리를 만들자!

※ 만약 기한이 지날 경우, 비공개 레포지토리를 삭제하거나 공개 레포지토리로 변경해야 한다.
 그 땐 학생이 아니라 갓-발자가 되었을 것이므로 돈을 내고 이용하자.


[Visual Studio] 코드로 중단점을 걸어보자 Lu's…〃 Programing。


 디버깅을 할 때 중단점은 굉장히 중요한 요소이다. 원하는 위치에, 원하는 조건과 원하는 때(호출된 위치)에 프로그램을 중단시킬 수 있기 때문이다.

 Visual Studio에선, 중단점/책갈피/열린 파일 등의 정보는 .suo(solution user option) 파일에 저장이 된다. 이 파일은 협업 시 공유할 필요가 없다.

 협업 중, 또는 코드만 보관하는 등 중단점을 저장할 방법이 없다면 debugapi.h 에 있는 함수를 사용하여 중단점을 걸 수 있다.



WINBASEAPI VOID WINAPI DebugBreak(VOID);

라고 돼 있는 함수이고, 자세한 내용은 MSDN 참고.



 끝, 인줄 알았지만 할 게 더 남았다. 이 함수를 호출하면 중단점이 트리거 되는데, 문제는 디버깅을 하지 않을 때 발생한다.

해당 함수는 중단점을 무조건 실행하기 때문에 디버거가 연결되지 않으면 죽어버린다. 

따라서 현재 프로세서가 디버거와 연결되어 있는지 확인할 필요가 있다.

 당연히, 그런 코드 역시 존재한다. debugapi.h에 위치한다.



WINBASEAPI BOOL WINAPI IsDebuggerPresent(VOID);

자세한 내용은 MSDN 참고.


 
 요약하자면, 코드 수준에서 중단점을 찍는 방법은 다음 코드와 같다.



    if (IsDebuggerPresent()) DebugBreak();



x86, x64 플랫폼에서 사용이 가능하니 __asm int 3; 같은 코드는 naver...



 + debugapi.h 는 winbase.h 에 포함되어 있고, winbase.h 는 windows.h 에 포함되어 있다.
 + 디버깅 중 출력이 필요한 일이 있다면 콘솔 창을 쓰지 말고 OutputDebugString() 함수를 쓰자


1 2 3 4 5 6 7 8 9 10 다음