<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>발아 바라 프로그래밍</title>
    <link>https://pppgod.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 24 Aug 2026 07:57:42 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>pppgod</managingEditor>
    <image>
      <title>발아 바라 프로그래밍</title>
      <url>https://t1.daumcdn.net/cfile/tistory/996D7A3B5CCB157520</url>
      <link>https://pppgod.tistory.com</link>
    </image>
    <item>
      <title>[언어] Typescript, 왜 시대에 역행하는가?</title>
      <link>https://pppgod.tistory.com/65</link>
      <description>&lt;p&gt;&amp;nbsp;제목을 보고 당황스러운 사람들이 꽤 많을것이라고 생각할 것이다. 누가 뭐래도 Javascript 언어의 대세는 Typescript 이기 때문이다. 그런데도 이 글을 쓰는 이유는 왜 Javascript 에서 Typescript 로 넘어가는지 생각해보는 시간을 갖기 위해서다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Typesciprt와 Javascript&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;누가뭐래도 Typescript 를 사용하는 이유는 Type 을 제공하지 않는 Javascript 에서 형식을 지정할 수 있기 때문이다. 왜 굳이 Type 이 필요한지 의문이 들 수 있는데, Java 와 같은 형식이 있는 언어를 사용하던 개발자들에게 Type 이 없다는 것은 낯설고 거북스러울 수 밖에 없다.&amp;nbsp;그런 개발자들에게 Typescript 는 축복일 수 밖에 없다. &lt;span style=&quot;color: #333333;&quot;&gt;무엇보다도 형식(Type)을 지정하면 &lt;b&gt;가독성&lt;/b&gt;이 좋아지기 때문에 많은 개발자들이 Typescript 를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그런데 시대에 역행한다고 한다는건 무슨 소리인가? 도대체 시대의 흐름은 무엇이길래 이런 이야기를 하는걸까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 언어의 흐름&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;업무적으로나 학업으로 쉽게 접할 수 있는 언어인 C++ 을 먼저 살펴보자. C++ 에 추가된 키워드 중에는 auto 라는 것이 있다. auto는 아래와 같은 경우에 주로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1599303416769&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::unordered_map&amp;lt; MyDataId, std::vector&amp;lt; MyDataClass &amp;gt; &amp;gt; myMap;

// auto를 사용하지 않는 버전
for(std::unordered_map&amp;lt; MyDataId, std::vector&amp;lt; MyDataClass &amp;gt; &amp;gt;::const_iterator it : myMap) {
    ...
}

// auto를 사용한 버전
for(auto it : myMap) {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 변수 it 의 형식을 지정하는 것이 과연 가독성이 좋다고 말할 수 있는가? 오히려 auto 를 사용했을때 코드의 가독성은 더 증가하였다. 뿐만 아니라 myMap 의 형식이 변경 되었을때 auto 를 사용한 경우에는 it 의 형식을 변경할 필요가 없어졌다.&lt;/p&gt;
&lt;p&gt;Java 개발자들에게 많은 지지를 받고 있는 kotlin 에서도 마찬가지이다. 형식을 특별히 지정해주지 않으면 auto 와 같이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Typescript는 흐름의 역행하는 것인가?&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;새로운 언어들은 기본적으로 auto 와 같은 자동 형식 연역을 제공하고, 기존의 언어들조차 자동 형식 연역 기능을 추가하였다. &lt;b&gt;그런데 유독 왜 Javascript 에서만 Typescript 라는 새로운 언어를 만들어서까지 형식을 제공해주려는걸까?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Javascript의 특징&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;그 이유를 이해하기 위해서는 &lt;b&gt;Javascript 언어의 특징을 이해해야한다.&lt;/b&gt; Javascript 는 가장 대표적인 인터프리터 언어다. &lt;b&gt;다시 말하면 컴파일러가 없다는 의미이다.&lt;/b&gt; 그렇기에 타입이란 것이 존재할 필요가 없다. 어차피 컴파일러가 없어 실행 전에 미리 문제를 확인할 수 없기 때문이다. 만약 타입이 있다고 해도 코드가 실행이 되어야지만 타입 체크를 할 수가 있고 런타임 중에 타입 체크를 하기에는 성능에도 좋지 않다. 그래서 Javascript 는 런타임 중에 primitive type 의 경우에만 타입 체크를 실행한다.(string, int와 같은) 덕분에 Javascript의 진입 장벽을 낮추는데 기여할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;하지만 Javascript 를 오랫동안 사용하다보니 위 장점이 단점으로 나타났다. 문법이나 변수명을 잘못 입력한 경우 그 코드가 실행이 되어야지만 문제가 있다는 것을 알 수 있다. 특별한 케이스에서만 사용되는 코드라던가 라이브러리를 사용하는 경우에 라이브러리에서 필요로하는 타입을 알기 어려워 문제를 놓치고 갈 수 있다. 이는 사용자에게 악영향을 끼칠 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;그래서 Microsoft 는 Javascript에게 컴파일러와 같은 것을 만들어주기로 하였고, 그 결과가 바로 Typescript 이다. 우리가 Typescript 를 작성하면 일종의 컴파일러가 Javascript 로 코드를 변환시키는데, 이 과정에서 타입을 검사하게 된다. 결과적으로 Typescript 는 Javascript 를 베이스로 사용하기 때문에 C++ 의 auto 와 같은 기능을 사용하는데에도 문제가 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C++에서 auto를 사용하는데 성능 문제가 없는 이유는?&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;C++의 경우에는 Javascript와 다르게 auto가 런타임 속도에 문제를 일으키지 않는다. 그 이유는 &lt;b&gt;컴파일러가 auto 를 적절한 타입으로 연역시키기 때문이다.&lt;/b&gt; 덕분에 컴파일 속도에는 영향을 미칠 수 있지만 런타임 속도에는 문제가 없다. 컴파일러를 사용하는 언어라면 C++과 같이 동작할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;앞선 내용을 정리하면 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;인터프리터 언어인 Javascript는 타입 문제를 런타임 중에 확인 가능하다.&lt;/b&gt;&lt;/li&gt;
&lt;li data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Typescript는 Javascript의 컴파일러와 같은 역할로 런타임 전에 타입 체크를 해준다.&lt;/b&gt;&lt;/li&gt;
&lt;li data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;컴파일러가 있는 언어의 경우, 컴파일 시에 타입을 연역시켜주어 런타임 속도에 문제가 없다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍</category>
      <category>auto</category>
      <category>C++</category>
      <category>JavaScript</category>
      <category>type</category>
      <category>typescript</category>
      <category>인터프리터 언어</category>
      <category>컴파일러</category>
      <category>타입</category>
      <category>프로그래밍 언어</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/65</guid>
      <comments>https://pppgod.tistory.com/65#entry65comment</comments>
      <pubDate>Sat, 5 Sep 2020 21:51:55 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine 4] Unreal 서버의 기본 개념</title>
      <link>https://pppgod.tistory.com/64</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;언리얼 엔진4의 서버는 간단하면서도 매우 복잡한 구조로 이루어져 있다. 서버를 프로젝트에 적용하기 위해서는 많은 노력이 필요하다. 기본적으로 언리얼에서 제공하는 수많은 도큐먼트들을 정독해야만 한다. 무작정 코드를 따라하더라도 원하는대로 동작하지 않을 수 있고, 문제가 발생한 경우 이를 알아차리기란 어렵다. 이번 포스트의 목표는 공식 도큐먼트를 읽기 전에 언리얼 서버의 기본 구조를 감으로나마 익히게 해주는 것이다. 이를 읽고 많은 도움이 되었으면 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버와 클라이언트의 관계&lt;/h3&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;가장 먼저 이해해야하는 것은 &lt;b&gt;서버와 클라이언트의 관계&lt;/b&gt;라 말할 수 있다. 언리얼에서 &lt;b&gt;서버는 절대 권력의 존재&lt;/b&gt;이다. 서버에서 일어나는 일들은 모든 클라이언트가 따라한다. &lt;b&gt;즉 클라이언트는 오직 서버에서 일어나는 일들을 따라할 뿐이다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반대로 클라이언트에서 일어나는 일들은 오직 해당하는 클라이언트에서만 변화가 일어날뿐 다른 클라이언트와 서버에서는 아무런 변화가 없다. 그렇다면 왜 언리얼은 이런 구조를 갖추었을까?&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;클라이언트는 서버와 다르게 유저가 프로그램에 직접 접근이 가능하기 때문에 클라이언트가 해킹될 가능성이 있다. 만일 어떤 클라이언트에서 다른 사용자의 캐릭터를 마음대로 죽일 수 있다면 해당 유저는 영문도 모르게 자신의 캐릭터가 죽는 것을 보게될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라이언트의 권한&lt;/h3&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 글을 읽고나면 어떻게 유저가 캐릭터를 조종하는지 의문이 들 것이다. 유저가 캐릭터를 조종하는 것은 유저의 클라이언트에 요청하는 것인데, 위의 이야기대로라면 서버에서는 아무런 동작도 일어나지 않을것이기 때문이다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;언리얼은 &lt;b&gt;클라이언트에게 아주 약간의 권한&lt;/b&gt;을 준다. 오직 클라이언트가 조종하고 있는 액터에 대해서만 명령할 수 있도록 해준다. 덕분에 움직이거나 공격하는 동작들이 모든 클라이언트에서 볼 수 있게 된다. 예시를 들어보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-04-04 오후 6.06.29.png&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QJvYh/btqC9xnNzbD/p716M3Y1eExxFR8m6sq3s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QJvYh/btqC9xnNzbD/p716M3Y1eExxFR8m6sq3s1/img.png&quot; data-alt=&quot;공격 애니메이션 재생 흐름&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QJvYh/btqC9xnNzbD/p716M3Y1eExxFR8m6sq3s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQJvYh%2FbtqC9xnNzbD%2Fp716M3Y1eExxFR8m6sq3s1%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-04-04 오후 6.06.29.png&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;1000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공격 애니메이션 재생 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;유저가 키보드나 마우스로 공격 요청&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;클라이언트는 서버에 공격 요청&lt;/span&gt;&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;클라이언트가 권한이 있으므로 서버 요청 가능&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;클라이언트가 권한이 없는 경우 서버 요청 불가능&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;서버는 모든 클라이언트에 공격 애니메이션을 재생하라고 요청&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span&gt;&amp;nbsp;위 방법을 통하지 않고서는 모든 클라이언트에 공격 명령을 전달할 방법이 없다. 클라이언트는 모든 클라이언트에 직접 접근할 수 없기 때문에 항상 서버를 통해서 전달해야만 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Unreal Engine 4</category>
      <category>Replicate</category>
      <category>RPC</category>
      <category>Server</category>
      <category>Unreal</category>
      <category>공격</category>
      <category>서버</category>
      <category>애니메이션</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/64</guid>
      <comments>https://pppgod.tistory.com/64#entry64comment</comments>
      <pubDate>Tue, 31 Mar 2020 18:39:11 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 42. 삽입 대신 생성 삽입을 고려하라</title>
      <link>https://pppgod.tistory.com/63</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;i&gt;&lt;b&gt;C++11&lt;/b&gt; &lt;/i&gt;로 오면서 &lt;i&gt;&lt;b&gt;push_back&lt;/b&gt; &lt;/i&gt;과 같은 역할을 하는 함수 &lt;i&gt;&lt;b&gt;emplace_back&lt;/b&gt; &lt;/i&gt;이라는 함수가 등장한 것을 볼 수 있다. 왜 이 함수가 등장했는지 어떠한 장점과 단점이 있는지 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583330383047&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::vector&amp;lt;std::string&amp;gt; vs;
vs.push_back(&quot;xyzzy&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드에서 함수로 전달되는 타입은 &lt;i&gt;&lt;b&gt;std::string&lt;/b&gt;&lt;/i&gt; 이 아닌 문자열 리터럴이다. 템플릿에 선언된 타입과는 다른 타입이라는 것이다. 컴파일러는 &lt;b&gt;형식 불일치&lt;/b&gt;를 인지하고 문자열 리터럴의 형식을 다음과 같이 변환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1583330498740&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vs.push_back(std::string(&quot;xyzzy&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;이것이 의미하는 것은 &lt;b&gt;임시 스트링 객체&lt;/b&gt;가 생성된다는 것이다. 또 벡터 내부적으로 원소를 집어넣을때 객체를 한 번 더 생성한다. 심지어 함수의 실행이 끝나면 &lt;b&gt;임시 스트링 객체의 소멸자&lt;/b&gt;가 호출된다. 내부에서 객체를 생성하는데 굳이 &lt;b&gt;임시 스트링 객체&lt;/b&gt;를 생성할 필요가 있을까? 지금의 코드는 매우 비효율적이다.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&amp;nbsp;emplace_back&lt;/b&gt;&lt;/i&gt; 의 경우에는 어떨까?&lt;/p&gt;
&lt;pre id=&quot;code_1583330873908&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vs.emplace_back(&quot;xyzzy&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;i&gt;&lt;b&gt;push_back&lt;/b&gt; &lt;/i&gt;과 코드는 똑같지만 컴파일러가 &lt;b&gt;임시 스트링 객체&lt;/b&gt;를 생성하지 않는다. 함수 내부에서 원소를 넣을때 객체 생성 될 뿐이다. 이와 같은 형식을 &lt;b&gt;생성 삽입&lt;/b&gt;이라고 부른다. 심지어 문자열 리터럴이 아닌 경우에도 &lt;i&gt;&lt;b&gt;push_back&lt;/b&gt; &lt;/i&gt;과 똑같은 역할을 하며, 때로는 더 효율적으로 동작하기도 한다. 그렇다면 항상 &lt;b&gt;생성 삽입&lt;/b&gt;을 써도 되는 것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;안타깝게도 그렇지 않다. 삽입 함수가 더 빠르게 실행되는 상황도 존재한다. 여러가지의 요인이 존재하는데 어떤 경우인지 알아보자.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너의 내부 구현 차이&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;컨테이너에 원소를 추가할 때 어떠한 방식으로 추가하느냐의 차이이다. 객체 &lt;b&gt;생성&lt;/b&gt;을 통해 추가할 수도 있고, &lt;b&gt;이동 배정&lt;/b&gt;을 통해 추가될 수 있다. &lt;b&gt;이동 배정&lt;/b&gt;을 위해서는 항상 원본 객체가 필요하다. 그래서 &lt;b&gt;임시 객체&lt;/b&gt;가 생성되어 &lt;b&gt;생성 삽입&lt;/b&gt;의 장점이 사라지는 것이다. &lt;b&gt;노드 기반 컨테이너&lt;/b&gt;들은 거의 항상 &lt;b&gt;삽입&lt;/b&gt;을 통해 추가한다. 그 외에 컨테이너는 3가지 밖에 존재하지 않는다. &lt;i&gt;&lt;b&gt;(std::vector, std::deque, std::string)&lt;/b&gt;&lt;/i&gt; 이 컨테이너들의 &lt;i&gt;&lt;b&gt;emplace&lt;/b&gt;&lt;/i&gt; 함수가 배정을 사용하고, &lt;i&gt;&lt;b&gt;emplace_back&lt;/b&gt;&lt;/i&gt; 의 경우에는 &lt;b&gt;생성&lt;/b&gt;을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인수의 형식과 템플릿 형식이 다른 경우&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;앞서 보았던 내용이다. 만일 인수의 형식과 템플릿 형식이 같다면 임시 객체의 생성과 소멸이 일어나지 않기 때문에 &lt;b&gt;생성 삽입&lt;/b&gt;이 빠를 이유가 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 값과의 중복을 허용하지 않은 경우&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;std::map, std::set 과 같이 중복을 허용하지 않은 경우를 의미한다. 해당 조건이 문제되는 이유는 생성 삽입의 경우에 노드를 생성해서 중복을 체크한다. 만약 중복이라면 노드를 파괴시키는데 이는 생성과 파괴 비용을 발생시킨다. 이는 &lt;b&gt;삽입 함수&lt;/b&gt;보다 &lt;b&gt;생성 삽입&lt;/b&gt;에서 더 자주 발생한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성 삽입의 문제점&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;이번에는 &lt;b&gt;생성 삽입&lt;/b&gt;의 문제점을 알아보겠다. 다음과 같은 컨테이너가 존재한다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1583333633752&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::list&amp;lt;std::shared_ptr&amp;lt;widget&amp;gt;&amp;gt; ptrs;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;그리고 &lt;b&gt;커스텀 삭제자&lt;/b&gt;를 통해서 해제되어야 하는 객체 원소를 삽입한다고 가정하자. 이 같은 경우에는 &lt;i&gt;&lt;b&gt;make_shared &lt;/b&gt;&lt;/i&gt;함수를 사용할 수 없다. 그렇다면 다음과 같이 구현될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1583333791748&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 커스텀 삭제자
void killWidget(Widget* pWidget);

// 첫번째 방식
ptrs.push_back(std::shared_ptr&amp;lt;Widget&amp;gt;(new Widget, killWidget));

// 두번째 방식
ptrs.push_back({ new Widget, killWidget });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;첫번째와 두번째 방식 모두 임시 객체가 생성된다. 그런데 &lt;i&gt;&lt;b&gt;push_back&lt;/b&gt;&lt;/i&gt; 함수를 수행하던 중 메모리가 부족해져 예외가 발생한다고 가정하자. 예외가 발생하면 생성되었던 임시 객체 또한 소멸되며 메모리가 정상적으로 해제된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;그런데 만일 &lt;i&gt;&lt;b&gt;emplace_back&lt;/b&gt;&lt;/i&gt; 을 사용해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583333908509&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ptrs.emplace_back(new Widget, killWidget);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;i&gt;&lt;b&gt;new Widget &lt;/b&gt;&lt;/i&gt;은 오른값이기 때문에 생성된 포인터가 &lt;i&gt;&lt;b&gt;emplace_back&lt;/b&gt; &lt;/i&gt;안으로 완벽 전달된다. 이제 메모리가 부족하여 예외가 발생했다고 하자. 예외가 &lt;i&gt;&lt;b&gt;emplace_back&lt;/b&gt; &lt;/i&gt;밖으로 전달되며 &lt;i&gt;&lt;b&gt;Widget&lt;/b&gt;&lt;/i&gt; 에 접근할 수 있는 포인터가 사라지게 된다. 즉 &lt;b&gt;댕글링 포인터&lt;/b&gt;가 발생하는 것이다. 이처럼 컨테이너를 다룰때는 효율성 보다 예외 안전성에 보다 신경을 써야한다. 위와 같은 문제를 해결하기 위해서는 외부에서 객체를 생성해서 함수로 전달하는 방법으로 해결할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1583334370089&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::shared_ptr&amp;lt;Widget&amp;gt; spw(new Widget, killWidget);

ptrs.push_back(std::move(spw));
ptrs.emplace_back(std::move(spw));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 외에도 또 다른 문제점이 있다. 다음의 예를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583334452937&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::vector&amp;lt;std::regex&amp;gt; regexes;

regexes.emplace_back(nullptr);  // 정상 동작

regexes.push_back(nullptr);     // 컴파일 에러&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;정규표현식들을 저장하는 컨테이너를 생성했다. 그런데 &lt;i&gt;&lt;b&gt;emplace_back&lt;/b&gt;&lt;/i&gt; 과 &lt;i&gt;&lt;b&gt;push_back&lt;/b&gt;&lt;/i&gt; 은 서로 다른 양상을 보인다. 그 전에 &lt;i&gt;&lt;b&gt;nullptr&lt;/b&gt; &lt;/i&gt;가 들어가는 것이 정상적으로 들어가는지 생각해보자. nullptr 가 정규표현식이라는 것은 매우 비정상적이다. 어떻게 이런 동작이 가능한걸까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;정규표현식의 생성자를 먼저 알아보아야 한다. 정규표현식의 생성자 중에서 &lt;i&gt;&lt;b&gt;const char*&lt;/b&gt;&lt;/i&gt; 를 받는 생성자가 &lt;i&gt;&lt;b&gt;explicit&lt;/b&gt;&lt;/i&gt; 으로 선언되어 있다. 따라서 다음과 같은 코드는 정상 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1583334774519&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::regex r = nullptr;        // 컴파일 에러

regexes.push_back(nullptr);    // 컴파일 에러

std::regex r(nullptr);         // 컴파일 성공&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;생성 삽입의 경우에는 첫번째 코드를 수행하는 것과 마찬가지이기 때문에 컴파일 에러가 발생하는 것이고 삽입 생성의 경우에는 마지막 라인을 수행하는 것이여서 컴파일에 성공한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>C++</category>
      <category>Effective</category>
      <category>emplace_back</category>
      <category>Modern</category>
      <category>push_back</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/63</guid>
      <comments>https://pppgod.tistory.com/63#entry63comment</comments>
      <pubDate>Thu, 5 Mar 2020 00:17:19 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 41. 이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달을 고려하라</title>
      <link>https://pppgod.tistory.com/62</link>
      <description>&lt;p&gt;&amp;nbsp;매개변수를 받는 방법에 대해서 우리는 &lt;b&gt;왼값, 오른값, 보편 참조&lt;/b&gt;를 이용해서 전달하는 방법을 배웠다. 특히 &lt;b&gt;왼값&lt;/b&gt;과 &lt;b&gt;오른값&lt;/b&gt; 모두 구현하는 경우에는 &lt;b&gt;보편 참조&lt;/b&gt;를 이용하여 구현하였다. 그런데 &lt;b&gt;보편 참조&lt;/b&gt;를 이용하게 되면 우리가 원하지 않는 타입에 대해서도 인스턴스화되고, 보편 참조로 전달할 수 없는 경우에도 난감한 상황이 생기게 된다. 그런데 우리가 앞서 보았던 방법 외에도 가장 기초적인 값으로 전달하는 방법이 있다. 다음의 코드를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583236422868&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Widget {
public:
  void addName(std::string newName)
  { names.push_back(std::move(newName)); }
  ...
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드는 어떠한 문제도 발생하지 않는다. 하지만 이전부터 값 전달의 성능은 좋지 않다고 알려져왔다. 실제로 &lt;i&gt;&lt;b&gt;C++98 &lt;/b&gt;&lt;/i&gt;에서는 비용이 컸다. 그런데 &lt;i&gt;&lt;b&gt;C++11&lt;/b&gt;&lt;/i&gt; 로 오면서 왼값일 때에만 복사 생성되고, 오른값일 때에는 이동 생성된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 비교&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;매개변수 전달 방법을 3가지로 나누어 성능을 비교해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583238217332&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 오버로딩
class Widget {
public:
  void addName(const std::string&amp;amp; newName)
  { names.push_back(newName); }
  
  void addName(std::string&amp;amp;&amp;amp; newName)
  { names.push_back(std::move(newName); }
  
  ...
  
  private:
    std::vector&amp;lt;std::string&amp;gt; names;
};

// 보편 참조
class Widget {
public:
  template&amp;lt;typename T&amp;gt;
  void addName(T&amp;amp;&amp;amp; newName)
  { names.push_back(std::forward&amp;lt;T&amp;gt;(newName)); }
  
  ...
};

// 값 전달
class Widget {
public:
  void addName(std::string newName)
  { names.push_back(std::move(newName)); }
  
  ...
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;중복적재(오버로딩)&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;&amp;nbsp;왼값&lt;/b&gt;과 &lt;b&gt;오른값&lt;/b&gt; 모두 참조로 전달되므로 인수로 전달되는 비용은 없다. 함수 내에서 &lt;b&gt;왼값&lt;/b&gt;의 경우에는 &lt;b&gt;복사 1회&lt;/b&gt;가 수행되고 &lt;b&gt;오른값&lt;/b&gt;의 경우에는 &lt;b&gt;이동 1회&lt;/b&gt; 수행된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;보편 참조&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;&amp;nbsp;오버로딩&lt;/b&gt;과 연산 시점과 비용이 똑같다. 함수로 전달하는 비용이 없고 본문에서 &lt;b&gt;복사&lt;/b&gt;와 &lt;b&gt;이동&lt;/b&gt;이 일어난다. 만일 &lt;i&gt;&lt;b&gt;std::string&lt;/b&gt; &lt;/i&gt;이 아닌 타입의 경우에는 &lt;i&gt;&lt;b&gt;std::string&lt;/b&gt;&lt;/i&gt; 의 복사와 이동이 0회 이상 일어날 수 있다. 그러나 여기서는 &lt;i&gt;&lt;b&gt;std::string&lt;/b&gt; &lt;/i&gt;의 경우에만 생각하자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;값 전달&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;앞서 설명한대로 &lt;b&gt;왼값&lt;/b&gt;과 &lt;b&gt;오른값&lt;/b&gt;으로 나누어 생각해야한다. &lt;b&gt;왼값&lt;/b&gt;의 경우에는 인수로 전달하는데 &lt;b&gt;복사 1회&lt;/b&gt;가 일어나고, &lt;b&gt;오른값&lt;/b&gt;의 경우에는 &lt;b&gt;이동 1회&lt;/b&gt;가 발생한다. 함수 본문에서는 두 가지 경우 모두 &lt;b&gt;이동 1회&lt;/b&gt;가 발생한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;성능의 비교로 알아보았듯이 &lt;b&gt;값 전달&lt;/b&gt;이 장점만 있는 것은 아니다. 성능 뿐만 아니라 &lt;b&gt;이동 전용 형식&lt;/b&gt;의 경우에는 우리가 값 전달을 고려할 필요가 없다. &lt;b&gt;오른값&lt;/b&gt;으로 매개변수를 받도록 만들었다면 인수를 전달하는데 드는 비용이 없기 때문이다. 또한 만일 &lt;i&gt;&lt;b&gt;addName&lt;/b&gt;&lt;/i&gt; 함수 내에 조건문에 따라 삽입을 하지 않는다면 쓸데없는 복사나 이동 연산을 하게 되는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배정문&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;b&gt;'='&lt;/b&gt; 연산을 사용하는 경우를 &lt;b&gt;배정문&lt;/b&gt;이라고 한다. 이 경우에는 좀 더 복잡해진다. 코드를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583240684990&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Password {
public:
  explicit Password(std::string pwd)
  : text(std::move(pwd)) {}
  
  void changeTo(std::string newPwd)
  { text = std::move(newPwd): }
  
  ...
  
private:
  std::string text;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드는 패스워드를 담고있는 객체이다. 생성자에서는 우리가 앞서 본 그대로이다. &lt;b&gt;배정문&lt;/b&gt;에서는 과연 어떻게 될까? 인수로 전달되는 것은 왼값이나 오른값에 따라 다른 것은 분명하다. 문제는 &lt;b&gt;배정문&lt;/b&gt;을 수행할 때 기존에 저장되어 있던 메모리를 해제하고 새로운 객체를 이동한다. 또한 만일 기존에 저장된 메모리가 새로 배정되는 메모리보다 크다면 굳이 메모리를 해제할 필요가 없다. 만약 우리가 값 전달이 아닌 중복적재 방식을 이용했다면 할당과 해제를 생략할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1583241957217&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Password {
public:
  ...
  
  void changeTo(const std::string&amp;amp; newPwd)
  {
    text = newPwd;
  }
  
  ...
private:
  std::string text;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;이 이외에도 값 전달은 &lt;i&gt;&lt;b&gt;slice off&lt;/b&gt;&lt;/i&gt; 문제가 발생할 수 있다. 다형성과 관련된 문제로 포인터 타입의 경우에는 이런 문제가 발생하지 않는다. 값 전달을 사용하기에는 위험한 점이 너무나도 많기 때문에 주의해서 사용해야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>C++</category>
      <category>call by value</category>
      <category>Effective</category>
      <category>Modern</category>
      <category>값 전달</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/62</guid>
      <comments>https://pppgod.tistory.com/62#entry62comment</comments>
      <pubDate>Tue, 3 Mar 2020 22:45:33 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 40. 동시성에는 std::atomic을 사용하고, volatile은 특별한 메모리에 사용하라</title>
      <link>https://pppgod.tistory.com/61</link>
      <description>&lt;p&gt;&amp;nbsp;프로그래머들은 종종 &lt;i&gt;&lt;b&gt;volatile&lt;/b&gt;&lt;/i&gt; 과 &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt;&lt;/i&gt; 을 혼동한다. 그러나 이 두 개는 서로 전혀 다른 역할을 한다. &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt;&lt;/i&gt; 은 원자성을 보존해주기 위해서 사용된다. 다시 말하자면 &lt;b&gt;mutex&lt;/b&gt; 없이도 두 개의 스레드가 동시에 접근해도 &lt;b&gt;data race&lt;/b&gt; 문제가 발생하지 않는다. volatile 은 컴파일러가 최적화하며 특정 라인을 수행하지 않는 경우가 없도록 해준다. 코드로 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1582639290569&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;volatile int vi(0);
++vi;
--vi;

std::cout &amp;lt;&amp;lt; vi;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드의 출력 결과는 당연히 0이다. 값은 초기와 다르지 않다. 우리가 생각하기에도 무의미한 계산을 한 것이다. 컴파일러는 최적화를 하며 두 개의 라인을 무시할 수 있다. 그런데 만약 임베디드나 특정한 상황에서 저 계산식은 무시가 되면 안되는 경우가 발생한다. 그런 경우에 사용하는 것이 &lt;i&gt;&lt;b&gt;volatile&lt;/b&gt;&lt;/i&gt; 이다. 컴파일러가 무시할 가능성을 없애주는 것이다. 그렇다고해서 &lt;b&gt;data race&lt;/b&gt; 문제는 해결되지 않는다. 두 개의 스레드가 동시에 접근은 가능하다는 의미이다. 반대로 &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt; &lt;/i&gt;에서는 무의미한 계산을 무시할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드 재배치&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;또다른 차이점으로는 코드 재배치라는 컴파일러 최적화가 있다. 컴파일러는 코드 라인의 순서를 임의대로 바꿀 수 있다. 레지스터에 적재하고 내리는데 사용하는 시간을 최적화하기 위해서 말이다. 그런데 std::atomic 에서는 이야기가 달라진다. &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt; &lt;/i&gt;을 기록하는 라인이 수행될 때는 그 이전에 나타난 라인들이 수행되어서는 안된다. 이것은 &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt; &lt;/i&gt;의 특성이다. 다시 말하자면 &lt;i&gt;&lt;b&gt;volatile&lt;/b&gt; &lt;/i&gt;은 그럴 필요가 없다는 것이다. 코드 재배치로 라인이 변경될 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;std::atomic&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;좀 더 &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt;&lt;/i&gt; 의 특징들을 살펴보자. &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt;&lt;/i&gt; 은 복사와 이동 연산을 지원하지 않는다. 그 이유는 간단하다. 원자성을 보존해야 하는게 주 목적인데 복사를 위해 읽고 기록하는 작업을 원자적으로 계산하는 하드웨어는 지원하지 않기 때문이다. 다행히 복사를 하는 방법이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1582641049725&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::atomic&amp;lt;int&amp;gt; y(x.load());
y.store(x.load());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;레지스터를 이용하여 최적화도 가능한데 &lt;b&gt;C++17&lt;/b&gt; 부터는 의미가 없기 때문에 넘어가도록 하겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;우리는 &lt;i&gt;&lt;b&gt;std::atomic&lt;/b&gt; &lt;/i&gt;과 &lt;i&gt;&lt;b&gt;volatile&lt;/b&gt; &lt;/i&gt;의 차이점에 대해서 알아보았다. 이 두 개는 서로 전혀 다른 역할을 한다는 것을 우리는 알 수 있었다. 따라서 두 개는 한꺼번에 사용도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1582641192650&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;volatile std::atomic&amp;lt;int&amp;gt; val;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>C++</category>
      <category>Effective</category>
      <category>Modern</category>
      <category>std::atomic</category>
      <category>volatile</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/61</guid>
      <comments>https://pppgod.tistory.com/61#entry61comment</comments>
      <pubDate>Tue, 25 Feb 2020 23:34:46 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 39. 단발성 사건 통신에는 void 미래 객체를 고려하라</title>
      <link>https://pppgod.tistory.com/60</link>
      <description>&lt;p&gt;&amp;nbsp;두가지 스레드에서 하나의 스레드의 작업이 끝나야만 시작되는 스레드가 있다고 하자. 이 같은 상황에서 작업이 다 끝났음을 알려주는 방법은 무엇일까? 한 가지 명백한 방법은 &lt;b&gt;조건 변수&lt;/b&gt;를 사용하는 것이다. 이 변수를 조건에 따라 저장하는 과제를 &lt;b&gt;검출 과제&lt;/b&gt;라고 하고, 이 조건에 반응하는 과제를 &lt;b&gt;반응 과제&lt;/b&gt;라고 하자. 어떤 방법이 있는지 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;std::condition_variable&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;i&gt;&lt;b&gt;std &lt;/b&gt;&lt;/i&gt;에서 제공하는 방법으로 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1582551088439&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::condition_variable cv;
std::mutex m;

// 검출 과제
{
  ...
  cv. notify_one();
}

// 반응 과제
{
  ...
  {
    std::unique_lock&amp;lt;std::mutex&amp;gt; lk(m);
    cv.wait(lk);
    ...
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드는 잘 작동하는 것처럼 보인다. 그러나 몇몇 아쉬운 점이 있다. 바로 &lt;b&gt;뮤텍스&lt;/b&gt;를 사용한다는 점이다. &lt;b&gt;검출 과제&lt;/b&gt;와 &lt;b&gt;반응 과제&lt;/b&gt;가 동시에 같은 변수에 접근하는 일은 일어나지 않는다. 따라서 &lt;b&gt;뮤텍스&lt;/b&gt;를 사용할 필요가 전혀 없다. 그런데 이보다 더 큰 문제가 있다. 만일 반응 과제가 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt;&lt;/i&gt; 을 하기 전에 &lt;i&gt;&lt;b&gt;notify&lt;/b&gt; &lt;/i&gt;를 하게 되면 반응 과제는 무한히 기다리게 된다. 또한 만일 가짜 기상이 일어나는 경우에 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt;&lt;/i&gt; 이 풀려버리게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;bool 플래그&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;코드를 먼저 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1582552581384&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::atomic&amp;lt;bool&amp;gt; flag(false);

// 검출 과제
{
  ...
  flag = true;
}

// 반응 과제
{
  ...
  while (!flag);
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드는 &lt;i&gt;&lt;b&gt;flag&lt;/b&gt;&lt;/i&gt; 가 &lt;i&gt;&lt;b&gt;true&lt;/b&gt;&lt;/i&gt; 가 될 때까지 폴링을 하게 된다. 즉, 쓸데없는 자원 낭비를 한다는 의미이다. 이를 방지하기 위해서는 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt;&lt;/i&gt; 을 사용하는 수 밖에 없다. 바로 이전에 사용했던 &lt;i&gt;&lt;b&gt;std::condition_variable&lt;/b&gt;&lt;/i&gt; 과 함께 사용하는 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1582553447328&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::condition_variable cv;
std::mutex m;

bool flag(false);

// 검출 과제
{
  ...
  {
    std::lock_guard&amp;lt;std::mutex&amp;gt; g(m);
    flag = true;
  }
}

// 반응 과제
{
  ...
  {
    std::unique_lock&amp;lt;std::mutex&amp;gt; lk(m);
    cv.wait(lk, [] { return flag; });
    ...
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위와 같은 방법으로는 지속적인 폴링도 일어나지 않으며, 가짜 기상이 발생하더라도 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt;&lt;/i&gt; 상태를 유지한다. 그러나 &lt;i&gt;&lt;b&gt;mutex&lt;/b&gt;&lt;/i&gt; 를 사용해야 한다는 점과 &lt;i&gt;&lt;b&gt;bool&lt;/b&gt; &lt;/i&gt;변수까지 써야한다는 점은 무언가 깔끔하지 않다. 또한 &lt;b&gt;반응 과제&lt;/b&gt;에서 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt; &lt;/i&gt;이 먼저 호출되어야 한다는 점은 변하지 않았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;std::promise&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;앞선 항목에서 살펴보았던 문제를 모두 해결하는 방법이다. 코드는 아주 간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1582554229192&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::promise&amp;lt;void&amp;gt; p;

// 검출 과제
{
  ...
  p.set_value();
}

// 반응 과제
{
  ...
  p.get_future().wait();
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;한가지 주의해야할 점은 힙 메모리 관리를 해야한다는 점과 공유 상태가 존재한다는 점이다. 더 중요한 것은 &lt;i&gt;&lt;b&gt;std::promised&lt;/b&gt; &lt;/i&gt;를 한 번만 설정할 수 있다는 점이다. 즉 &lt;b&gt;단발성&lt;/b&gt;이라는 의미이다. 이러한 특징을 생각하면 &lt;b&gt;void 미래 객체&lt;/b&gt;를 사용하는 것이 합리적이다.&lt;/p&gt;
&lt;pre id=&quot;code_1582555462632&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::promise&amp;lt;void&amp;gt; p;

// 반응 과제
void react();

// 검출 과제
void detect()
{
  ThreadRAII tr(
    std::thread([]
                {
                  p.get_future().wait();
                  react();
                }),
    ThreadRAII::DtorAction::join
  );
  ...
  p.set_value();
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;더 나아가 반응 과제가 여러 개이어도 문제가 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1582555586814&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::promise&amp;lt;void&amp;gt; p;

void detect()
{
  auto sf = p.get_furue().share();
  
  std::vector&amp;lt;std:;thread&amp;gt; vt;
  
  for (int i = 0; i &amp;lt; threadsToRun; ++i) {
    vt.emplace_back([sf]{ sf.wait();
                          react(); });
  }
  
  ...
  p.set_value();
  ...
  
  for (auto&amp;amp; t : vt) {
    t.join();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>C++</category>
      <category>Effective</category>
      <category>Future</category>
      <category>Modern</category>
      <category>단발성</category>
      <category>통신</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/60</guid>
      <comments>https://pppgod.tistory.com/60#entry60comment</comments>
      <pubDate>Mon, 24 Feb 2020 23:46:58 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 38. 스레드 핸들 소멸자들의 다양한 행동 방식을 주의하라</title>
      <link>https://pppgod.tistory.com/59</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;b&gt;항목 37&lt;/b&gt;에서 &lt;b&gt;합류 가능한 스레드&lt;/b&gt;가 동작중에 스레드 객체가 소멸되면 프로그램이 종료된다고 하였다. 스레드와 비슷한 &lt;i&gt;&lt;b&gt;async&lt;/b&gt; &lt;/i&gt;객체가 남기는 &lt;i&gt;&lt;b&gt;future&lt;/b&gt;&lt;/i&gt; 객체는 &lt;b&gt;암묵적 join&lt;/b&gt;과 &lt;b&gt;암묵적 detach&lt;/b&gt;와 비슷한 행동을 수행한다. 그럼에도 스레드와 다르게 프로그램이 종료된다거나 미정의 행동을 하지는 않는다. 어떻게 동작할 수 있을까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;공유 상태&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;우선 스레드의 결과가 어디에 저장되는지 살펴보아야 한다. 생각할 수 있는 가능성은 두가지일 것이다. 스레드를 생성한 쪽이거나 스레드로 생각할 수 있다. 그러나 두가지 모두가 아닌 외부에 저장된다. 그 이유를 생각해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스레드를 생성한 쪽에 저장되는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;위와 같은 경우에는 &lt;i&gt;&lt;b&gt;future&lt;/b&gt; &lt;/i&gt;객체에 저장될 것이다. 그런데 &lt;i&gt;&lt;b&gt;future&lt;/b&gt; &lt;/i&gt;객체는 &lt;i&gt;&lt;b&gt;std::shared_future&lt;/b&gt; &lt;/i&gt;를 생성할 수 있어 &lt;i&gt;&lt;b&gt;future&lt;/b&gt; &lt;/i&gt;객체가 파괴되어도 여러 번 복사할 수 있다. 그런데 만일 스레드의 결과가 복사를 지원하지 않는 경우, 다수의 미래 객체 중 어떤 것에 결과를 저장해야할지 알 수 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스레드에서 저장되는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;스레드에 저장되는 경우에는 &lt;i&gt;&lt;b&gt;std::promise &lt;/b&gt;&lt;/i&gt;에 저장될 것이다. 그런데 &lt;i&gt;&lt;b&gt;get&lt;/b&gt; &lt;/i&gt;함수를 통해 결과를 얻기 전에 스레드가 종료된다고 가정하면 스레드는 종료되며 &lt;i&gt;&lt;b&gt;std::promise&lt;/b&gt;&lt;/i&gt; 객체가 함께 소멸될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;위와 같은 이유로 스레드의 결과는 &lt;b&gt;공유 상태(shared state)&lt;/b&gt;라는 곳에 저장된다. &lt;b&gt;공유 상태를 알아야하는 이유는 공유 상태가 &lt;i&gt;future &lt;/i&gt;객체 소멸자의 행동을 결정하기 때문이다. &lt;/b&gt;그 규칙은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;std:: async를 통해서 시동된 비지연 과제에 대한 공유 상태를 참조하는 마지막 미래 객체의 소멸자는 과제가 완료될 때까지 차단된다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다른 모든 미래 객체의 소멸자는 그냥 해당 미래 객체를 파괴한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;위 규칙을 간단하게 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;공유 상태는 마지막 future 객체가 소멸될 때 소멸되고, 동시에 암묵적인 join을 수행한다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;암묵적인 join을 수행하기 때문에 큰 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;마지막 &lt;i&gt;&lt;b&gt;future&lt;/b&gt; &lt;/i&gt;객체가 소멸될 때, &lt;b&gt;암묵적인 join&lt;/b&gt;을 수행하며 &lt;i&gt;&lt;b&gt;async&lt;/b&gt; &lt;/i&gt;함수가 종료될 때까지 기다리는 것이다. 예를 들면 &lt;i&gt;&lt;b&gt;async&lt;/b&gt; &lt;/i&gt;로 부터 생성된 스레드가 무한 루프를 돌고 있다면, &lt;i&gt;&lt;b&gt;async&lt;/b&gt; &lt;/i&gt;를 호출한 스레드도 함께 멈추기 때문이다. 문제는 &lt;i&gt;&lt;b&gt;future&lt;/b&gt; &lt;/i&gt;객체를 생성하는 방법이 &lt;i&gt;&lt;b&gt;async&lt;/b&gt;&lt;/i&gt; 뿐이 아니라는 것이다. 그 중 하나가 &lt;i&gt;&lt;b&gt;std::packaged_task&lt;/b&gt;&lt;/i&gt; 이다. &lt;i&gt;&lt;b&gt;std::packaged_task &lt;/b&gt;&lt;/i&gt;는 다행히 &lt;i&gt;&lt;b&gt;async&lt;/b&gt;&lt;/i&gt; 에서 사용할 필요가 없다. 어차피 &lt;i&gt;&lt;b&gt;async&lt;/b&gt; &lt;/i&gt;에서 &lt;i&gt;&lt;b&gt;future&lt;/b&gt; &lt;/i&gt;객체를 생성하기 때문이다. 따라서 스레드에서 사용하는 것이 일반적이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;i&gt;&lt;b&gt;std::packaged_task&lt;/b&gt;&lt;/i&gt; 가 스레드에서 사용된다면 우리는 &lt;b&gt;암묵적인 join&lt;/b&gt;에 대해서 고려할 필요가 없어진다. 우리가 스레드를 종료할 때 3가지의 선택지로 종료한다. 각각의 선택지에 대해 &lt;i&gt;&lt;b&gt;future&lt;/b&gt;&lt;/i&gt; 객체의 소멸자가 수행할 행동은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;join을 수행하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;우리가 스레드에 join을 한다면 future 객체는 소멸될 때, 아무런 행동을 할 필요가 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;detach를 수행하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;우리가 스레드에 detach를 한다면 future 객체는 소멸될 때, 아무런 행동을 할 필요가 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;아무것도 하지 않는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스레드가 소멸될 때, 프로그램은 종료된다.&lt;/p&gt;
&lt;p&gt;따라서 우리는 future 객체의 소멸자에서 일어나는 일에 대해 고려할 필요가 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>async</category>
      <category>C++</category>
      <category>detach</category>
      <category>Effective</category>
      <category>Future</category>
      <category>Join</category>
      <category>Modern</category>
      <category>thread</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/59</guid>
      <comments>https://pppgod.tistory.com/59#entry59comment</comments>
      <pubDate>Sun, 9 Feb 2020 21:27:23 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 37. std::thread들을 모든 경로에서 합류 불가능하게 만들어라</title>
      <link>https://pppgod.tistory.com/58</link>
      <description>&lt;p&gt;&amp;nbsp;모든 스레드 객체는 &lt;b&gt;합류 가능&lt;/b&gt;이거나 &lt;b&gt;합류 불가능&lt;/b&gt;이다. 여기서 말하는 합류는 &lt;i&gt;&lt;b&gt;join&lt;/b&gt;&lt;/i&gt; 을 사용할 수 있는 상태를 말한다. &lt;b&gt;합류 불가능 스레드&lt;/b&gt;는 다음의 목록이 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 생성된 std::thread&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실행할 함수가 없이 생성된 객체는 바탕 실행 스레드와는 대응되지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다른 std::thread 객체로 이동된 후의 std::thread 객체&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이동이 완료되면 기존의 스레드는 대응되는 스레드가 존재하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;join 에 의해 합류된 std::thread&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;join 이 완료되면 대응되는 스레드가 존재하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;detach 에 의해 탈착된 std::thread&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스레드 객체와 바탕 스레드와의 연결이 끊어져 합류 불가능 상태가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;합류 가능성이 중요한 이유는 &lt;b&gt;합류 가능 스레드&lt;/b&gt;의 &lt;b&gt;소멸자&lt;/b&gt;가 호출되면 프로그램이 &lt;b&gt;종료&lt;/b&gt;되기 때문이다. 예제를 통해 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1581241188535&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;constexpr auto tenMillion = 10'000'000;

bool doWork(std::function&amp;lt;bool(int)&amp;gt; filter,
            int maxVal = tenMillion)
{
   std::vector&amp;lt;int&amp;gt; goodVals;
   
   std::thread t([&amp;amp;filter, maxVal, &amp;amp;goodVals]
                 {
                   for (auto i = 0; i &amp;lt;= maxVal; ++i)
                   { if (filter(i)) goodVals.push_back(i); }
                 });

  auto nh = t.native_handle();
  ...
  
  if (conditionsAreSatisfied()) {
    t.join();
    performComputation(goodVals);
    return true;
  }
  
  return false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위의 코드에서 &lt;i&gt;&lt;b&gt;conditionsAreSatisfied()&lt;/b&gt; &lt;/i&gt;함수가 &lt;i&gt;&lt;b&gt;true&lt;/b&gt; &lt;/i&gt;를 반환하면 문제는 발생하지 않는다. 그러나 &lt;i&gt;&lt;b&gt;false&lt;/b&gt; &lt;/i&gt;를 반환하는 경우에는 &lt;i&gt;&lt;b&gt;t&lt;/b&gt;&lt;/i&gt; 의 소멸자가 호출되어 프로그램이 종료된다. 왜 다른 방식으로 동작하지 않고 프로그램이 종료될까? 만약 프로그램이 종료되지 않는 경우를 살펴보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;암묵적 join&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;스레드의 소멸자가 스레드가 완료되기를 기다리는 방식이다. 합리적으로 보이지만 사실은 그렇지 않다. &lt;i&gt;&lt;b&gt;conditionsAreSatisfied()&lt;/b&gt;&lt;/i&gt; 가 이미 &lt;i&gt;&lt;b&gt;false&lt;/b&gt; &lt;/i&gt;를 반환해서 함수가 종료되어도 마땅한 상황임에도 스레드가 종료되길 기다려야 하는 것은 합리적이지 않다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;암묵적 detach&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;스레드가 실행중인 상태에서 &lt;i&gt;&lt;b&gt;false&lt;/b&gt;&lt;/i&gt; 를 반환하여 &lt;i&gt;&lt;b&gt;doWork&lt;/b&gt;&lt;/i&gt; 함수가 종료되었다고 가정해보자. &lt;i&gt;&lt;b&gt;doWork&lt;/b&gt;&lt;/i&gt; 가 종료되며 &lt;b&gt;지역변수&lt;/b&gt;들도 함께 소멸된다. 문제는 위의 코드에서 스레드는 &lt;i&gt;&lt;b&gt;goodVals&lt;/b&gt;&lt;/i&gt; 라는 &lt;b&gt;지역변수&lt;/b&gt;를 참조로 사용하고 있다. 그렇게되면 스레드는 소멸된 변수에 접근하게 되어 미정의 행동을 하게 되는데, 이를 디버깅하기란 매우 어려울 것이다.&lt;/p&gt;
&lt;p&gt;결과적으로 두가지 경우 모두 적절하지 않으므로 프로그램을 종료하기로 결정되었다. 다행히도 우리는 &lt;b&gt;RAII&lt;/b&gt; 방식을 사용하여 문제를 해결할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1581242532725&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ThreadRAII {
public:
  enum class DtorAction { join, detach };
  
  ThreadRAII(std::thread&amp;amp;&amp;amp; t, DtorAction a)
  : action(a), t(std::move(t)) {}
  
  ~ThreadRAII()
  {
    if (t.joinable()) {
      if (action == DtorAction::join) {
        t.join();
      } else {
        t.detach();
      }
    }
  }
  
  ThreadRAII(ThreadRAII&amp;amp;&amp;amp;) = default;
  ThreadRAII&amp;amp; operator=(ThreadRAII&amp;amp;&amp;amp;) = default;
  
  std::Thread&amp;amp; get() { return t; }
  
private:
  DtorAction action;
  std::thread t;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>C++</category>
      <category>detach</category>
      <category>Effective</category>
      <category>Join</category>
      <category>Modern</category>
      <category>thread</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/58</guid>
      <comments>https://pppgod.tistory.com/58#entry58comment</comments>
      <pubDate>Sun, 9 Feb 2020 19:07:14 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 36. 비동기성이 필수일 때에는 std::laynch::async를 지정하라</title>
      <link>https://pppgod.tistory.com/57</link>
      <description>&lt;p&gt;이전 포스트에서 이야기한것처럼 std::async 는 스레드를 생성하지 않을 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pppgod.tistory.com/56&quot;&gt;https://pppgod.tistory.com/56&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;og-loading&quot; contenteditable=&quot;false&quot;&gt;불러오는 중입니다...&lt;/figure&gt;
&lt;p&gt;시동 방침을 통해 비동기로 수행되도록 할 수 있다. 시동 방침이 무엇인지 알아보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;std::launch::async&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 조건문을 사용하면 항상 스레드를 생성하게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;std::launch::deferred&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;해당 조건을 사용하면 &lt;i&gt;&lt;b&gt;get&lt;/b&gt;&lt;/i&gt; 이나 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt; &lt;/i&gt;을 호출할 때까지 함수가 실행되지 않는다.&lt;/p&gt;
&lt;p&gt;위의 두가지 시동 방침을 살펴보았는데, &lt;b&gt;기본 시동 방침&lt;/b&gt;은 과연 무엇일까? 정답은 두가지 조건을 모두 사용한다. 다시 말하면 함수는 비동기적으로 실행될 수도 동기적으로 실행될 수도 있다. 그런데 위와 같은 특성 때문에 문제가 발생할 수 있다. 다음을 살펴보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수가 지연 실행될 수도 있으므로, 비동기로 실행될지 예츠이 불가능하다.&lt;/li&gt;
&lt;li&gt;함수가 &lt;i&gt;&lt;b&gt;get&lt;/b&gt; &lt;/i&gt;이나 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt; &lt;/i&gt;을 호출하는 스레드와 다른 스레드에서 실행될지 알 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;get&lt;/b&gt; &lt;/i&gt;이나 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt; &lt;/i&gt;호출이 일어난다는 보장이 없기 때문에 함수가 실행되지 않을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;thread_local&lt;/b&gt; &lt;/i&gt;변수들과도 궁합이 잘 맞지 않는다. &lt;b&gt;스레드 지역 저장소(TLS)&lt;/b&gt;를 읽거나 쓰는 코드가 있을때, 언제 접근하는지 예측이 불가능하기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;심지어는 &lt;i&gt;&lt;b&gt;wait&lt;/b&gt;&lt;/i&gt; 기반 루프에도 영향을 미치기도 한다. &lt;i&gt;&lt;b&gt;wait_for&lt;/b&gt; &lt;/i&gt;나 &lt;i&gt;&lt;b&gt;wait_until&lt;/b&gt; &lt;/i&gt;을 사용하면 &lt;i&gt;&lt;b&gt;std::future_status::deffered&lt;/b&gt; &lt;/i&gt;라는 값이 반환되기 때문이다. 이 때문에 무한 루프가 발생하기도 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1580635669846&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using namespace std::literals;

void f()
{
  std::this_thread::sleep_for(1s);
}

auto fut = std::async(f);

while (fut.wait_for(100ms) !=
       std::future_status::ready)
{
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;위 코드에서 &lt;i&gt;&lt;b&gt;f&lt;/b&gt;&lt;/i&gt; 가 기존 스레드와 동시에 실행되면 문제가 발생하지 않지만 지연되는 경우에는 &lt;i&gt;&lt;b&gt;wait_for&lt;/b&gt;&lt;/i&gt; 의 결과가 항상 &lt;i&gt;&lt;b&gt;std::future_status::deffered&lt;/b&gt;&lt;/i&gt; 를 반환하여 무한 루프가 발생하게 된다. 해결 방법은 다음과 같이 구현이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1580635871842&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;auto fut = std::async(f);

if (fut.wait_for(0s) ==
    std::future_status::deffered)
{
  ...
} else {
  while (fut.wait_for(100ms) !=
         std::future_status::ready) {
    ...
  }
  
  ...
}
  &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;이제 지연이 되더라도 코드는 문제 없이 동작하게 된다. 앞서 살펴봤던 내용에 따라 &lt;b&gt;기본 시동 방침&lt;/b&gt;은 다음의 상황에서만 적합하다는 것을 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;get&lt;/b&gt;&lt;/i&gt; 이나 &lt;i&gt;&lt;b&gt;wait &lt;/b&gt;&lt;/i&gt;를 호출하는 스레드와 반드시 동시적으로 실행되어야 하는 것은 아닐때&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;thread_local&lt;/b&gt; &lt;/i&gt;변수들을 읽소 쓰는지가 중요하지 않을때&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;futrure&lt;/b&gt;&lt;/i&gt; 객체에 대해 &lt;i&gt;&lt;b&gt;get &lt;/b&gt;&lt;/i&gt;이나 &lt;i&gt;&lt;b&gt;wait &lt;/b&gt;&lt;/i&gt;이 반드시 호출된다는 보장이 있거나 실행되지 않아도 괜찮은 경우&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;wait_for&lt;/b&gt; &lt;/i&gt;나 &lt;i&gt;&lt;b&gt;wait_until&lt;/b&gt; &lt;/i&gt;을 사용하는 코드에 지연 될 수 있다는 조건이 반영된 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;위 조건 중에서 하나라도 부합하지 않은 경우에는 반드시 비동기로 수행되도록 변경해야 한다. 방법은 간단하다. 시동방침을 이용하는 것이다. 만약 함수를 만들어서 구현하고 싶다면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1580636410757&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// C++11
template&amp;lt;typename F, typename... Ts&amp;gt;
inline
std::future&amp;lt;typename std::result_of&amp;lt;F(Ts...)&amp;gt;::type&amp;gt;
reallyAsync(F&amp;amp;&amp;amp; f, Ts&amp;amp;&amp;amp;... params)
{
  return std::async(std::launch::async,
                    std::forward&amp;lt;F&amp;gt;(f),
                    std::forward&amp;lt;Ts&amp;gt;(params)...);
}

// C++14
template&amp;lt;typename F, typename... Ts&amp;gt;
inline
auto
reallyAsync(F&amp;amp;&amp;amp; f, Ts&amp;amp;&amp;amp;... params)
{
  return std::async(std::launch::async,
                    std::forward&amp;lt;F&amp;gt;(f),
                    std::forward&amp;lt;Ts&amp;gt;(params)...);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>async</category>
      <category>C++</category>
      <category>deffered</category>
      <category>Effective</category>
      <category>Modern</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/57</guid>
      <comments>https://pppgod.tistory.com/57#entry57comment</comments>
      <pubDate>Sun, 2 Feb 2020 18:40:51 +0900</pubDate>
    </item>
    <item>
      <title>[Effective Modern C++] 항목 35. 스레드 기반 프로그래밍보다 과제 기반 프로그래밍을 선호하라</title>
      <link>https://pppgod.tistory.com/56</link>
      <description>&lt;p&gt;&amp;nbsp;비동기적으로 함수를 실행하는 방법은 &lt;i&gt;&lt;b&gt;std::thread&lt;/b&gt;&lt;/i&gt; 객체를 생성하는 방법과 &lt;i&gt;&lt;b&gt;std::async&lt;/b&gt;&lt;/i&gt; 객체를 생성하는 방법이다. &lt;i&gt;&lt;b&gt;std::thread&lt;/b&gt; &lt;/i&gt;객체를 생성하는 방법이 &lt;b&gt;스레드 기반 프로그래밍&lt;/b&gt;을 의미하고, &lt;i&gt;&lt;b&gt;std::async&lt;/b&gt; &lt;/i&gt;객체를 이용하는 방법이 &lt;b&gt;과제 기반 프로그래밍&lt;/b&gt;을 의미한다. 코드를 살펴보면 다음과 같은 차이가 존재한다.&lt;/p&gt;
&lt;pre id=&quot;code_1580626935176&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int doAsyncWork();

// 스레드 기반
std::thread t(doAsyncWork);

// 과제 기반
auto fut = std::async(doAsyncWork); // future 객체가 생성됨&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;과제 기반 프로그래밍에서 &lt;i&gt;&lt;b&gt;future&lt;/b&gt;&lt;/i&gt; 객체가 생성된다는 것에 집중해야 한다. doAsyncWork 함수가 종료되면 반환값이 존재하는 것을 살펴볼 수 있는데 스레드를 사용하는 경우에는 그 반환값에 접근이 불가능하다. 그에 비해 future 객체를 사용하면 반환값에 접근할 수 있다. 더 나아가 예외가 발생했는지도 알 수 있다. 보다 근본적인 차이는 스레드를 보다 추상적으로 사용할 수 있다는 의미이다. 이 말을 이해하기 위해서는 스레드에 대해서 알아보아야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;스레드는 3가지로 분류할 수 있다. 어떤 스레드들이 존재하는지 알아보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하드웨어 스레드&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;실제 연산을 수행하는 스레드를 &lt;b&gt;하드웨어 스레드&lt;/b&gt;라고 부른다. CPU 코어 하나당 하나 이상의 &lt;b&gt;하드웨어 스레드&lt;/b&gt;를 제공한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;소프트웨어 스레드&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;운영체제가 하드웨어 스레드를 효율적으로 사용하기 위해 만든 스레드를 의미한다. 하나의 소프트웨어 스레드가 &lt;b&gt;블로킹&lt;/b&gt;되면 다른 스레드를 실행시킨다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;std::thread&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;C++ 표준 라이브러리로 어떤 &lt;b&gt;소프트웨어 스레드&lt;/b&gt;에도 대응되지 않을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스레드의 사용의 주의점&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;스레드가 아무리 많아져도 운영체제가 알아서 잘 관리해줄것 같지만 사실은 그렇지 않다. &lt;b&gt;소프트웨어 스레드&lt;/b&gt;가 너무 많아지면 &lt;i&gt;&lt;b&gt;std::thread&lt;/b&gt;&lt;/i&gt; 를 생성할 때 예외가 발생한다. 그 이전에도 &lt;b&gt;과다구독(oversubscription)&lt;/b&gt;이 발생하는데 &lt;b&gt;소프트웨어 스레드&lt;/b&gt;가 &lt;b&gt;하드웨어 스레드&lt;/b&gt;가 많아지는 경우에 발생한다. 일반적으로는 성능 저하가 눈에 보이지 않지만 스레드가 늘어날수록 &lt;b&gt;context switch&lt;/b&gt; 가 발생하는 상황이 많아지고, CPU 캐시는 히트되지 않게되고 다음 스레드가 사용할 캐시 또한 오염되어 버린다. 문제를 해결할 방법은 &lt;b&gt;과다구독&lt;/b&gt;이 일어나지 않도록 하는 것인데 이는 쉽지 않다. 이러한 문제를 누군가에게 떠넘긴다면 문제는 간단해진다. 그 역할이 바로 &lt;i&gt;&lt;b&gt;std::async&lt;/b&gt;&lt;/i&gt; 이다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;std::async&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;이 라이브러리르 std::thread 와는 조금 다르게 동작한다. 스레드를 생성하지 않을수도 있다는 것이 그 차이점이다. 그렇지만 이것이 문제를 해결하는 근본적인 방법은 아니다. 그럼에도 스케쥴러가 현재 상황을 더 많이 파악하고 있을것은 분명하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;한가지 주의할 점은 스레드를 생성하지 않을수도 있기 때문에 반응성에 문제가 있을 수 있다. 이 때에는 시동 방침을 넘겨주어 반드시 스레드를 생성하도록 하는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드를 직접 다루어야 하는 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;바탕 스레드(소프트웨어 스레드) 적용 라이브러리의 API에 접근해야 하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응용 프로그램의 스레드 사용량을 최적화해야 하는, 그리고 할 수 있어야 하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C++ 동시성 API가 제공하는 것 이상의 스레드 적용 기술을 구현해야 하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;참고 서적&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;normal&quot;&gt;&lt;i&gt;스콧 마이어스, Effective Modern C++&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>C++/Effective Modern C++</category>
      <category>async</category>
      <category>C++</category>
      <category>Effective</category>
      <category>Modern</category>
      <category>thread</category>
      <author>pppgod</author>
      <guid isPermaLink="true">https://pppgod.tistory.com/56</guid>
      <comments>https://pppgod.tistory.com/56#entry56comment</comments>
      <pubDate>Sun, 2 Feb 2020 16:59:50 +0900</pubDate>
    </item>
  </channel>
</rss>