<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>loooong 님의 블로그</title>
    <link>https://lo88ng.tistory.com/</link>
    <description>loooong 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 7 Oct 2026 22:41:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>loooong</managingEditor>
    <item>
      <title>[UE5 개발일지] 친구 합류와 구조 완료를 분리하기 &amp;mdash; 이벤트와 Actor 생명주기 정리</title>
      <link>https://lo88ng.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;친구를 발견하면 편대에 합류해 플레이어를 따라오고, 함께 있는 동안 공동 최대 HP 보너스를 제공한다. 하지만 합류만으로 구조가 끝나는 것은 아니다. 안전지대까지 데려가 파견해야 구조가 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 작업에서는 최대 세 마리 편대, 안전지대 안에서 E로 편대 전원을 파견하는 기능, 합류&amp;middot;파견에 따른 공동 HP 보너스 추가와 제거를 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 구조 집계는 여전히 친구의 Following 전환을 기준으로 동작하고 있었다. 편대에 합류하기만 해도 구조 목표가 올라가면서, 실제 파견 행동과 미션 판정이 맞지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업에서는 구조 성공을 집계하는 시점을 파견 성공으로 옮겼다. 기존 편대&amp;middot;공동 HP 정리 흐름에 구조 이벤트와 친구 Actor 제거를 연결하고, 안전지대 중첩 처리도 보완했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 기능을 늘리기보다 &lt;b&gt;합류는 동행과 임시 HP 보너스, 파견은 구조 완료와 편대 정리&lt;/b&gt;라는 두 행동의 의미를 코드에서도 일치시키는 데 집중했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상태를 관찰하는 대신 성공한 행동을 보고하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구조는 친구의 상태를 주기적으로 확인해 Following 전환을 FriendRescued 이벤트로 변환했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Following은 동행 중이라는 상태이지 구조가 끝났다는 증거가 아니다. 구조 완료를 상태에서 추측하기보다, 파견을 실행하는 코드가 성공 시점에 직접 보고하도록 변경했다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;if (!Friend-&amp;gt;StartDeparture())
{
    continue;
}

RunEvents-&amp;gt;ReportFriendRescued(
    Friend,
    FriendId,
    Rarity);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 UpdateFriendStates()의 선언&amp;middot;정의&amp;middot;Tick 호출을 제거하고, SendCurrentFriendFormation()에서 파견 전환이 성공한 친구만 보고하도록 연결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReportFriendRescued()는 런 이벤트 관찰 중에만 처리한다. 친구 Actor별 기록의 bRescueReported로 중복 보고를 방어하고, 이벤트에는 물고기 ID&amp;middot;희귀도&amp;middot;Amount = 1을 담는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관찰 Tick이 오기 전에 합류와 파견이 빠르게 끝난 경우에도 기록을 생성한다. 구조 집계가 폴링 주기에 좌우되지 않도록 한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조 보고와 연출, 제거의 순서 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 판정 시점을 바꾸면서 친구 Actor의 생명주기도 함께 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 StartDeparture()가 상태 변경과 Blueprint 연출 호출을 함께 담당했다. 이번에는 이 함수가 Departing 상태로 전환하고 추종 참조와 슬롯을 정리하는 역할만 맡도록 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 PlayerPawn이 구조 보고부터 Actor 제거까지의 순서를 명시적으로 관리한다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;if (!Friend-&amp;gt;StartDeparture())
{
    continue;
}

// 성공한 친구만 편대에서 제거하고 HP 보너스 반환 대상으로 집계
FollowingFriends.RemoveAt(FriendIndex);
RemovedHealthBonus += HealthBonus;
++SentFriendCount;

// Actor가 제거되기 전에 구조 완료를 런 이벤트로 전달
if (UCF_RunEventSubsystem* RunEvents =
        UCF_RunEventSubsystem::Get(this))
{
    RunEvents-&amp;gt;ReportFriendRescued(
        Friend,
        FriendId,
        Rarity);
}

// UI와 Blueprint에는 ID&amp;middot;희귀도 데이터 전달
OnFriendDelivered.Broadcast(FriendId, Rarity);

// 별도의 출발 효과를 생성할 수 있는 Blueprint 훅
Friend-&amp;gt;ReceiveDepartureStarted();

// 처리가 끝난 게임플레이 Actor 제거
Friend-&amp;gt;Destroy();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;필요한 보고와 알림을 끝낸 뒤 Actor를 제거하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런 이벤트에는 구조 완료를 보고하고, UI와 Blueprint에는 OnFriendDelivered로 물고기 ID와 희귀도를 전달한다. 모든 친구의 파견 처리가 끝나면 성공한 친구들의 공동 최대 HP 보너스를 합산해 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReceiveDepartureStarted() 직후 친구 Actor가 제거되므로, 지속되는 출발 연출은 별도 효과나 연출용 Actor로 생성해야 한다. 제거될 친구 Actor에 Delay를 걸어 이후 동작을 이어가는 방식은 사용하지 않도록 계약을 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파견하면 공동 HP 보너스도 반환한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구 합류와 파견은 공동 HP에도 영향을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 작업에서 친구가 편대에 합류하면 플레이어의 최대 HP가 증가하고, 안전지대로 파견하면 해당 친구들이 제공한 최대 HP 보너스를 제거하도록 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파견할 때는 성공적으로 출발한 친구의 보너스만 합산해 제거한다. 현재 HP를 보너스만큼 그대로 차감하는 것이 아니라, 줄어든 최대 HP를 초과할 때만 보정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;파견 전: 현재 HP 140 / 최대 HP 175
보너스 75 제거
파견 후: 현재 HP 100 / 최대 HP 100

파견 전: 현재 HP 60 / 최대 HP 175
보너스 75 제거
파견 후: 현재 HP 60 / 최대 HP 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구를 구조했다고 해서 플레이어에게 추가 피해를 주지는 않되, 편대가 제공하던 임시 생존 보너스는 회수하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업에서는 이 기존 HP 정리 흐름에 &lt;b&gt;파견 성공 시 구조 이벤트 보고와 Actor 제거&lt;/b&gt;를 연결했다. 따라서 파견은 구조 기록뿐 아니라 편대 구성과 최대 HP까지 함께 정리하는 행동이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 녹화 중 2026-08-28 151356.gif&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c53EpT/dJMcahTjFVH/iDVTZIzLoG7XMROANP4KKk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c53EpT/dJMcahTjFVH/iDVTZIzLoG7XMROANP4KKk/img.gif&quot; data-alt=&quot;친구 합류 시 최대 HP가 증가하고 파견 후 보너스가 제거되는 장면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c53EpT/dJMcahTjFVH/iDVTZIzLoG7XMROANP4KKk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c53EpT/dJMcahTjFVH/iDVTZIzLoG7XMROANP4KKk/img.gif&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;414&quot; data-filename=&quot;화면 녹화 중 2026-08-28 151356.gif&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;친구 합류 시 최대 HP가 증가하고 파견 후 보너스가 제거되는 장면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;안전지대 안에 있다는 사실은 bool 하나로 부족했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안전지대 진입 여부를 bool 하나로 관리하던 방식에도 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A와 B가 겹친 영역에 들어간 뒤 A만 벗어나면, 여전히 B 안에 있어도 A의 Exit 이벤트가 파견 가능 상태를 false로 바꿨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 정보는 &amp;ldquo;마지막 이벤트가 Enter였는가?&amp;rdquo;가 아니라 &amp;ldquo;현재 어떤 안전지대 안에 있는가?&amp;rdquo;였다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;TSet&amp;lt;TWeakObjectPtr&amp;lt;ACF_FriendSafeZone&amp;gt;&amp;gt;
    ActiveFriendSafeZones;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 존의 진입&amp;middot;이탈을 집합에 반영하고, 유효한 존이 하나라도 있으면 파견 가능한 것으로 판단하도록 변경했다. 약한 참조를 사용해 안전지대 Actor가 제거된 경우도 유효성 검사로 구분한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;영구 무적과 시간제 무적도 분리했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구의 영구 무적과 향후 플레이어에게 사용할 짧은 무적을 같은 상태로 관리하지 않도록 분리했다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;bool IsInvulnerable() const
{
    return bIsPermanentlyInvulnerable
        || bHasTimedInvulnerability;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구는 BeginPlay()에서 영구 무적을 적용한다. 체력 데이터를 다시 초기화해도 영구 무적은 유지하고, 시간제 효과만 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간제 무적은 더 짧은 요청이 들어와도 기존 남은 시간을 줄이지 않도록 처리했다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;InvulnerabilityTimeRemaining = FMath::Max(
    InvulnerabilityTimeRemaining,
    Duration);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어 대시 자체는 아직 구현하지 않았다. 이번 변경은 서로 다른 무적 정책이 충돌하지 않도록 기반을 정리한 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업은 이미 있는 기능의 의미를 바로잡는 작업이었다. 합류 상태를 구조 성공으로 추측하던 코드를 성공한 행동의 명시적 보고로 바꾸고, 안전지대 bool을 실제 중첩 관계를 표현하는 집합으로 바꿨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 완료를 보고하는 시점과 Actor를 제거하는 순서를 명확히 해야 미션 집계와 Blueprint 연출도 같은 계약 위에서 동작할 수 있다는 점을 배웠다.&lt;/p&gt;</description>
      <category>프로젝트/Captain FiNN</category>
      <category>CaptainFiNN</category>
      <category>subsystem</category>
      <category>TWeakObjectPtr</category>
      <category>ue5</category>
      <category>게임개발일지</category>
      <category>상태관리</category>
      <category>언리얼C++</category>
      <category>언리얼엔진5</category>
      <category>이벤트설계</category>
      <category>자동화테스트</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/63</guid>
      <comments>https://lo88ng.tistory.com/63#entry63comment</comments>
      <pubDate>Wed, 16 Sep 2026 17:09:02 +0900</pubDate>
    </item>
    <item>
      <title>std::list &amp;mdash; 양방향 연결 리스트 컨테이너</title>
      <link>https://lo88ng.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;std::vector는 연속된 메모리를 사용하기 때문에 Random Access와 순차 탐색 성능이 뛰어나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 중간에 원소를 삽입하거나 삭제하면 뒤쪽 원소를 이동해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list는 이러한 문제를 다른 방식으로 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소를 연속적으로 저장하는 대신 &lt;b&gt;각각의 원소를 독립적인 Node&lt;/b&gt;로 만들고, Node끼리 연결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 std::list의 구조와 특징, Iterator 안정성, splice()와 sort() 같은 고유 기능을 살펴보고 마지막으로 vector와 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;1. std::list가 필요한 이유&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-1. vector의 중간 삽입&amp;middot;삭제 비용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 원소를 연속해서 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;[10][20][30][40] // 20과 30 사이에 25를 넣으려면

        &amp;darr;

[10][20][25][30][40] // 뒤쪽 원소를 이동해야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 중간 삽입은 일반적으로:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제 역시 동일하다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;[10][20][30][40]

20 삭제

[10][30][40] // 뒤쪽 원소를 앞으로 이동시켜야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-2. 원소를 이동하지 않는 자료구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linked List는 원소를 연속해서 저장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 각 원소를 Node에 저장하고 Node끼리 연결한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B와 C 사이에 X를 삽입하면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [X] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 C 이후의 원소들을 이동할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연결 관계만 수정&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL에서는 이러한 양방향 연결 리스트를:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;2. std::list란&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list 역시 vector, deque와 마찬가지로 Sequence Container에 속한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 데이터 저장 방식은 완전히 다르다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 원소를 독립적인 Node에 저장하고 이전 Node와 다음 Node를 연결하는 Doubly Linked List 기반의 Sequence Container다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 사용 방법은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::list&amp;lt;int&amp;gt; Numbers = {
    10, 20, 30
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. Doubly Linked List&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Node는 개념적으로 다음 정보를 가진다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;┌───────────────────┐
│ Prev Pointer      │
│ Data              │
│ Next Pointer      │
└───────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 양방향으로 이동할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;3. 내부 구조&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. Node 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 list가 있다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::list&amp;lt;int&amp;gt; Numbers = {
    10, 20, 30
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;[Prev | 10 | Next]
        ⇅
[Prev | 20 | Next]
        ⇅
[Prev | 30 | Next]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태로 연결되어 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-2. 비연속 메모리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Node는 메모리상 서로 다른 위치에 존재할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0x1000             0x8700             0x3200

[10] ───────────&amp;rarr; [20] ───────────&amp;rarr; [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;Begin + Index * sizeof(T)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 방식으로 원하는 원소의 주소를 계산할 수 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-3. Bidirectional Iterator&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list는 &lt;b&gt;Bidirectional Iterator&lt;/b&gt;를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;++It;
--It;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 앞뒤로 한 칸씩 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;It += 10; // n칸 떨어진 위치로 직접 이동할 수 있는 Random Access Iterator 를 요구함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 원하는 위치로 즉시 이동하는 Random Access는 지원하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;4. 주요 특징&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-1. Random Access를 지원하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드는 사용할 수 없다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;std::list&amp;lt;int&amp;gt; Numbers = {
    10, 20, 30
};

Numbers[2]; // 불가능&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n번째 원소로 이동하려면 순차적으로 이동해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;auto It = Numbers.begin();

// Iterator를 지정한 거리만큼 이동시키는 함수
std::advance(It, 2);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 시간복잡도는:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list의 Random Access가 O(N)인 것이 아니라, Random Access 자체를 지원하지 않으며 n번째 위치까지 이동하는 데 O(N)이 필요하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-2. 삽입&amp;middot;삭제 위치를 알면 O(1)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 상태에서:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B와 C 사이에 X를 넣으면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [X] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 정보만 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 삽입 위치 Iterator를 이미 가지고 있다면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;Numbers.insert(It, 25);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 자체는:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제도 마찬가지다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.erase(It);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제 위치 Iterator가 이미 있다면 O(1)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 삭제할 원소를 먼저 찾아야 한다면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;auto It = std::find(
    Numbers.begin(),
    Numbers.end(),
    20
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;탐색에 O(N)이 필요&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-3. 앞&amp;middot;뒤 삽입과 삭제가 O(1)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list는 &lt;b&gt;앞과 뒤 모두에서 삽입과 삭제를 O(1)에 수행할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Numbers.push_front(10);
Numbers.push_back(40);

Numbers.pop_front();
Numbers.pop_back();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, vector는 뒤쪽 삽입과 삭제만 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 list가 앞뒤 삽입&amp;middot;삭제에 유리하다고 해서 항상 더 빠른 것은 아니다. 실제 성능은 Cache Locality, Node Allocation, 순회 빈도 등의 영향도 함께 받는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-4. 메모리 오버헤드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Node는 데이터 이외에도 연결 정보를 가지고 있어야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Prev Pointer
Data
Next Pointer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 vector보다 원소당 메모리 오버헤드가 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 작은 타입일수록 이 차이가 커질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 64-bit 환경에서 포인터 하나가 일반적으로 8Byte라면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;int Data        4Byte
Prev Pointer    8Byte
Next Pointer    8Byte&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 padding 등도 추가될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;*padding : 메모리 정렬(alignment)을 맞추기 위해 객체 내부에 끼워 넣는 사용하지 않는 바이트&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Node 크기는 구현체와 alignment에 따라 달라진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-5. Reallocation이 없다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 capacity가 부족하면 더 큰 연속 저장 공간으로 이동해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 list는 새로운 Node를 하나 생성해 연결하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;기존

[A] ⇄ [B] ⇄ [C]

새 Node 삽입

[A] ⇄ [B] ⇄ [X] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 vector와 같은 전체 저장 공간 Reallocation은 발생하지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-6. Iterator / Reference Invalidation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list는 Iterator 안정성이 높은 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 원소를 삽입해도 20 Node의 위치는 바뀌지 않는다. 따라서 It은 계속 유효하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;list에 원소를 삽입해도 기존 원소를 가리키는 Iterator와 Reference는 일반적으로 무효화되지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제에서도 해당 원소를 가리키던 Iterator와 Reference만 무효화되고, 다른 Node를 가리키던 Iterator는 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;삽입
&amp;rarr; 기존 Iterator / Reference 유지

삭제
&amp;rarr; 삭제된 원소를 가리키던 것만 무효화&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4-7. 주요 멤버 함수&lt;/h2&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 266px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;함수&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;시간복잡도&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Access&lt;/td&gt;
&lt;td&gt;지원하지 않음&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n번째 원소까지 이동&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;front()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;첫 번째 원소 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;back()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;마지막 원소 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;push_front()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;앞쪽 삽입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;push_back()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;뒤쪽 삽입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;pop_front()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;첫 번째 원소 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;pop_back()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;마지막 원소 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;insert()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Iterator 위치에 삽입, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위치를 알고 있을 때&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;erase()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Iterator 위치의 원소 제거, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위치를 알고 있을 때&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;clear()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(N)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;모든 원소 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;size()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;원소 개수 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;empty()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(1)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;비어 있는지 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;splice()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;overload에 따라 다름&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Node 연결 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;sort()&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;O(N log N)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;list 자체 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insert()와 erase()의 O(1)은 &lt;b&gt;위치 Iterator를 이미 알고 있을 때&lt;/b&gt; 기준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;5. list::splice()&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splice()는 std::list의 대표적인 고유 기능이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 원소를 복사하거나 이동 생성하지 않고 &lt;b&gt;Node 자체의 연결 관계를 변경하여 다른 위치로&lt;/b&gt; 옮긴다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::list&amp;lt;int&amp;gt; A = {
    1, 2, 3
};

std::list&amp;lt;int&amp;gt; B = {
    10, 20, 30
};

auto It = B.begin();
++It;

// It &amp;rarr; 20

A.splice(
    A.end(),
    B,
    It
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// B가 소유하던 노드를 A 쪽으로 넘기는 것

A
[1] ⇄ [2] ⇄ [3] ⇄ [20]

B
[10] ⇄ [30]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 원소를 옮기는 경우 O(1)에 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, splice()의 모든 overload가 무조건 O(1)은 아니다. 범위를 옮기는 경우에는 상황에 따라 이동하는 원소 수에 비례하는 비용이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;7. list::sort()&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7-1. 왜 std::sort()를 사용할 수 없는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector에서는 다음이 가능하다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers = {
    4, 1, 3, 2
};

std::sort(
    Numbers.begin(),
    Numbers.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 list에서는 사용할 수 없다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::list&amp;lt;int&amp;gt; Numbers = {
    4, 1, 3, 2
};

// 불가능
std::sort(
    Numbers.begin(),
    Numbers.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7-2. Iterator Category 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::sort()는 Random Access Iterator를 요구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;It + N;
It - N;
It2 - It1;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 연산이 가능해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector의 Iterator는 이를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 list는 Bidirectional Iterator만 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;++It;
--It;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 가능하기 때문에 std::sort()의 요구조건을 만족하지 못한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7-3. list::sort()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 list 자체가 정렬 함수를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.sort();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내림차순도 가능하다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.sort(
    std::greater&amp;lt;int&amp;gt;()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도는:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;O(N log N)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list라서 std::sort()를 못 쓰는 것이 아니라,&lt;b&gt; std::sort()가 요구하는 Random Access Iterator를 list가 제공하지 않기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;8. Big-O와 실제 성능은 다를 수 있다&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8-1. list의 O(1) 삽입이 항상 빠른 것은 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표만 보면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;vector 중간 삽입
O(N)

list 중간 삽입
O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 list가 훨씬 좋아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현실에서는 반드시 그렇지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 list의 O(1)은 &lt;b&gt;삽입 위치 Iterator를 이미 알고 있을 때&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 위치를 먼저 찾는다면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Search O(N)
+
Insert O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실제 성능에는 Big-O 외에도 메모리 접근 방식이 큰 영향을 준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8-2. Cache Locality&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 원소가 연속되어 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[A][B][C][D][E]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 A를 읽을 때 주변 데이터도 Cache Line 단위로 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이후 B, C를 읽을 때 Cache Hit가 발생할 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 list는:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;[A] ───&amp;rarr; [B] ───&amp;rarr; [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 다음 Node가 메모리 어디에 존재할지 보장되지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8-3. 시간 지역성과 공간 지역성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 지역성 &amp;mdash; Temporal Locality&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 사용한 데이터를 가까운 시간 안에 다시 사용할 가능성이 높다는 특성이다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; 1000; ++i)
{
    Sum += Player.Health;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 데이터를 반복적으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공간 지역성 &amp;mdash; Spatial Locality&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 메모리에 접근했다면 주변 메모리에도 곧 접근할 가능성이 높다는 특성이다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[A][B][C][D]
 &amp;uarr;
A 접근 후 B, C 접근&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector 순회가 대표적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list는 Node가 비연속적으로 존재할 수 있기 때문에 이 공간 지역성을 활용하기 상대적으로 어렵다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8-4. Pointer Chasing&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list를 순회하려면 계속 다음 Node의 Pointer를 따라가야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Node
 &amp;darr;
Next Pointer
 &amp;darr;
다른 메모리 위치
 &amp;darr;
Next Pointer
 &amp;darr;
다른 메모리 위치&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 접근을 &lt;b&gt;Pointer Chasing&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node가 캐시에 없다면 매번 메모리 접근 지연이 발생할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8-5. Node Allocation 비용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 보통 하나의 연속된 저장 공간을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 list는 일반적으로 Node 단위의 메모리 할당이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같은 추가 비용이 발생할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Node Allocation
Deallocation
Allocator Metadata
Memory Fragmentation&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 성능에서는 시간복잡도뿐 아니라 Cache Locality, Allocation 비용, 원소 크기, 접근 패턴까지 고려해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;9. std::forward_list&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::forward_list는:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Singly Linked List 기반의 Sequence Container다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list는 양방향이다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forward_list는 한 방향으로만 연결된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] &amp;rarr; [B] &amp;rarr; [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Node는 개념적으로:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Data
Next Pointer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 list보다 연결 정보에 필요한 메모리 오버헤드를 줄일 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9-1. list와 forward_list 비교&lt;/h2&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;std::list&lt;/td&gt;
&lt;td&gt;std::forward_list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구조&lt;/td&gt;
&lt;td&gt;Doubly Linked List&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Singly Linked List&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node Pointer&lt;/td&gt;
&lt;td&gt;Prev + Next&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Next&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iterator&lt;/td&gt;
&lt;td&gt;Bidirectional&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Forward&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;++It&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;--It&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push_front()&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push_back()&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;제공하지 않음&lt;/b&gt;&lt;br /&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;만 &lt;/span&gt;&lt;span&gt;관리하고 &lt;/span&gt;&lt;span&gt;tail&lt;/span&gt;&lt;span&gt;을 &lt;/span&gt;&lt;span&gt;별도로 &lt;/span&gt;&lt;span&gt;관리하지 &lt;/span&gt;&lt;span&gt;않도록 &lt;/span&gt;&lt;span&gt;설계됨&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Access&lt;/td&gt;
&lt;td&gt;지원하지 않음&lt;/td&gt;
&lt;td&gt;지원하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 오버헤드&lt;/td&gt;
&lt;td&gt;상대적으로 큼&lt;/td&gt;
&lt;td&gt;상대적으로 작음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9-2. insert_after() / erase_after()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forward_list는 이전 Node로 이동할 수 없다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] &amp;rarr; [B] &amp;rarr; [C]
       &amp;uarr;
       It&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B 뒤에 X를 넣는 것은 쉽다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] &amp;rarr; [B] &amp;rarr; [X] &amp;rarr; [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;B 앞에 삽입하려면&lt;/b&gt; A의 Next를 수정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 현재 B만 알고 있다면 이전 Node인 A를 바로 찾을 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;insert_after();
erase_after();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9-3. size()가 없다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::forward_list에는 size() 멤버 함수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소 개수를 O(1)에 반환하려면 컨테이너가 별도의 원소 개수 상태를 유지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forward_list는 최소한의 메모리 오버헤드를 목표로 하기 때문에 이를 제공하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요하다면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::distance(
    List.begin(),
    List.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 계산할 수 있지만:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;10. vector vs list&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10-1. 구조 및 시간복잡도 비교&lt;/h2&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;std::vector&lt;/td&gt;
&lt;td&gt;std::list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;내부 구조&lt;/td&gt;
&lt;td&gt;동적 배열&lt;/td&gt;
&lt;td&gt;&lt;b&gt;양방향 연결 리스트&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원소 저장&lt;/td&gt;
&lt;td&gt;연속&lt;/td&gt;
&lt;td&gt;&lt;b&gt;비연속&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Access&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;지원하지 않음&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n번째 원소 접근&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중간 삽입&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;Iterator가 있다면 O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중간 삭제&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;Iterator가 있다면 O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push_back()&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Amortized O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push_front()&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10-2. Iterator 안정성 비교&lt;/h2&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;vector&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삽입&lt;/td&gt;
&lt;td&gt;Reallocation/원소 이동으로 무효화 가능&lt;/td&gt;
&lt;td&gt;기존 원소 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;삭제 위치 이후 무효화&lt;/td&gt;
&lt;td&gt;삭제된 원소만 무효화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reallocation&lt;/td&gt;
&lt;td&gt;발생 가능&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10-3. 메모리 및 캐시 특성&lt;/h2&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;vector&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Locality&lt;/td&gt;
&lt;td&gt;좋음&lt;/td&gt;
&lt;td&gt;일반적으로 좋지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pointer Chasing&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원소별 Pointer&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;Prev / Next&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 오버헤드&lt;/td&gt;
&lt;td&gt;상대적으로 작음&lt;/td&gt;
&lt;td&gt;큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allocation&lt;/td&gt;
&lt;td&gt;연속 저장 공간 중심&lt;/td&gt;
&lt;td&gt;Node 단위 할당&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list는 양방향 연결 리스트 기반의 &lt;b&gt;Sequence Container&lt;/b&gt;다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[A] ⇄ [B] ⇄ [C]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연속된 메모리를 사용하지 않기 때문에 &lt;b&gt;Random Access는 지원하지 않지&lt;/b&gt;만, 삽입&amp;middot;삭제 위치 Iterator를 알고 있다면 Node의 연결 관계만 변경하여 O(1)에 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Reallocation이 없기 때문에 기존 Iterator와 Reference의 안정성이 높다는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Node Pointer 오버헤드
Pointer Chasing
낮은 공간 지역성
Node Allocation 비용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등으로 인해 실제 순회 성능은 vector보다 불리한 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 list는 Big-O만 보고 선택하는 자료구조가 아니라, &lt;b&gt;Iterator 안정성이나 Node 단위 연결 작업이 실제 요구사항에 맞는지&lt;/b&gt;를 보고 선택하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forward_list는 여기서 Prev Pointer를 제거한 더 가벼운 단방향 연결 리스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다음 글에서는 vector처럼 Random Access를 지원하면서도 앞과 뒤의 삽입&amp;middot;삭제를 효율적으로 수행하는 &lt;b&gt;std::deque&lt;/b&gt;의 구조를 살펴본다.&lt;/blockquote&gt;</description>
      <category>C++/STL과 표준 라이브러리</category>
      <category>forward_list</category>
      <category>list</category>
      <category>Node</category>
      <category>random access</category>
      <category>Reallocation</category>
      <category>Sequence Container</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/62</guid>
      <comments>https://lo88ng.tistory.com/62#entry62comment</comments>
      <pubDate>Sun, 6 Sep 2026 19:57:59 +0900</pubDate>
    </item>
    <item>
      <title>std::vector &amp;mdash; 동적 배열 컨테이너</title>
      <link>https://lo88ng.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;좋아. 이번 글은 범위가 꽤 넓어서, &lt;b&gt;vector를 중심으로 설명하고 std::array는 비교용으로 붙이는 구조&lt;/b&gt;가 가장 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 지금 초안에는 몇 군데 사실관계와 표현을 다듬는 게 좋다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vector 성장 배율을 &lt;b&gt;1.5~2.5배라고 고정적으로 쓰면 안 됨&lt;/b&gt;. 표준은 성장 배율을 규정하지 않는다.&lt;/li&gt;
&lt;li&gt;push_back()의 시간복잡도는 &amp;ldquo;평균 O(1)&amp;rdquo;보다는 &quot;amortized O(1)&quot; (애머타이즈드)이라고 쓰는 게 정확하다.&lt;/li&gt;
&lt;li&gt;resize()는 size를 변경하지만 &lt;b&gt;필요하면 capacity도 증가할 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;shrink_to_fit()은 capacity를 줄이는 &lt;b&gt;non-binding request&lt;/b&gt;다.&lt;/li&gt;
&lt;li&gt;insert()/erase()의 invalidation은 단순히 &amp;ldquo;이후 iterator 무효화&amp;rdquo;가 아니라 &lt;b&gt;reallocation 여부에 따라 규칙이 달라진다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;std::array의 크기는 &amp;ldquo;컴파일 타임&amp;rdquo;이라고 설명해도 좋지만, 더 정확히는 &lt;b&gt;타입의 일부인 compile-time constant&lt;/b&gt;다.&lt;/li&gt;
&lt;li&gt;vector 객체가 반드시 pointer + size + capacity 필드를 가진다고 단정하면 안 된다. &lt;b&gt;개념적으로 그렇게 관리한다고 표현&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 가장 자주 사용되는 STL 컨테이너 중 하나가 std::vector다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 &lt;b&gt;원소를 연속된 메모리에 저장하면서도 실행 중 크기를 변경할 수 있는 동적 배열 컨테이너&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 빠른 Random Access와 캐시 친화적인 메모리 구조를 유지하면서, 크기 관리와 메모리 재할당을 자동화한다는 점이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;1. vector의 등장 배경&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 스타일 배열은 연속된 메모리에 데이터를 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int Numbers[5];&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연속된 메모리를 사용하기 때문에 인덱스를 이용한 접근이 빠르다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Numbers[2];&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;크기를 작게 잡으면 공간이 부족할 수 있다.&lt;/li&gt;
&lt;li&gt;크기를 크게 잡으면 사용하지 않는 공간이 낭비될 수 있다.&lt;/li&gt;
&lt;li&gt;더 큰 저장 공간이 필요하면 새로운 배열을 만들고 데이터를 직접 옮겨야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 배열이 있다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int Numbers[5] = {1, 2, 3, 4, 5};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 다섯 칸을 모두 사용하고 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[1][2][3][4][5]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 6을 추가하고 싶다고 해서 기존 배열 자체를 다음과 같이 늘릴 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요하다면 더 큰 저장 공간을 새로 확보하고 기존 데이터를 복사해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;새로운 더 큰 배열 생성
        &amp;darr;
기존 데이터 복사
        &amp;darr;
기존 배열 정리&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::vector는 이러한 과정을 내부적으로 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소가 늘어나 현재 저장 공간이 부족해지면 더 큰 연속 메모리를 확보하고 기존 원소를 새로운 공간으로 이동 또는 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;2. vector의 내부 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::vector는 원소들을 &lt;b&gt;연속된 메모리 공간&lt;/b&gt;에 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers = {10, 20, 30, 40};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소 저장 영역을 개념적으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Address

0x1000

┌────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │
└────┴────┴────┴────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 원소가 연속적으로 배치되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 인덱스를 알면 원하는 원소의 위치를 바로 계산할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;원소 주소
=
시작 주소 + (Index &amp;times; sizeof(T))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 int가 4Byte이고 첫 번째 원소의 주소가 0x1000이라고 가정하면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Numbers[2]

0x1000 + (2 &amp;times; 4)

= 0x1008&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 vector의 Random Access는:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;3. vector 객체와 실제 원소 저장 공간&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;vector 객체 자체&lt;/b&gt;와 &lt;b&gt;실제 원소 저장 공간&lt;/b&gt;은 구분해서 이해하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 다음과 같은 구조를 생각할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;vector 객체
┌──────────────────┐
│ 원소 저장소 정보  │
│ size 정보        │
│ capacity 정보    │
└───────┬──────────┘
        │
        ▼

별도의 연속 저장 공간
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │    │    │
└────┴────┴────┴────┴────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 표준은 vector 구현 내부에 반드시&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;pointer
size
capacity&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 세 멤버를 저장하라고 규정하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 일반적인 구현에서는 원소 저장 영역과 현재 크기, 확보된 공간을 알 수 있는 정보를 내부적으로 관리한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;4. size와 capacity&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector를 이해하려면 size와 capacity의 차이를 반드시 알아야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;size&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 실제로 존재하는 원소 개수다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers = {10, 20, 30};

std::cout &amp;lt;&amp;lt; Numbers.size();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;capacity&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 저장 공간을 다시 할당하지 않고 저장할 수 있는 최대 원소 개수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;size     = 3
capacity = 5&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라면 메모리를 다음처럼 생각할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │    │    │
└────┴────┴────┴────┴────┘
  실제 원소       여유 공간&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;size
&amp;rarr; 현재 존재하는 원소 수

capacity
&amp;rarr; 현재 저장 공간에 들어갈 수 있는 원소 수&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 다음 관계가 성립한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;size &amp;lt;= capacity&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;5. vector의 주요 특징&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.1 연속된 메모리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 배열처럼 원소를 연속적으로 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[A][B][C][D][E]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 순차 접근 시 공간 지역성이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 메모리를 읽을 때 일반적으로 하나의 값만 가져오는 것이 아니라 주변 데이터를 &lt;b&gt;Cache Line&lt;/b&gt; 단위로 캐시에 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 시스템에서는 Cache Line이 흔히 64Byte인 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음처럼 연속된 원소를 순회하는 코드는 캐시를 효율적으로 활용하기 좋다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;for (const auto&amp;amp; Value : Numbers)
{
    Process(Value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 vector는 list처럼 각 원소가 서로 떨어진 위치에 존재할 수 있는 Node 기반 자료구조보다 &lt;b&gt;순차 탐색 성능에서 유리한 경우가 많다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.2 Random Access &amp;mdash; O(1)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스로 원하는 원소에 바로 접근할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Numbers[100];&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도는:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.3 뒤쪽 삽입 &amp;mdash; Amortized O(1)&lt;/h2&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.push_back(10);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 뒤에 공간이 남아 있다면 단순히 마지막 위치에 원소를 추가하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;[1][2][3][ ][ ]
         &amp;darr;
[1][2][3][4][ ] // O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 size == capacity 상태라면 새로운 저장 공간이 필요하다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[1][2][3]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 4를 추가하면 더 큰 연속 메모리를 확보해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;기존 저장 공간

[1][2][3]

      &amp;darr;

새 저장 공간

[1][2][3][4][ ][ ]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 원소들을 이동 또는 복사해야 하므로 이 한 번의 삽입은 &lt;b&gt;O(N)&lt;/b&gt;이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 매번 재할당하는 것은 아니기 때문에 여러 번의 push_back()을 전체적으로 평균내면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Amortized O(1)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;6. 왜 push_back()은 Amortized O(1)인가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 공간이 부족할 때 보통 현재보다 더 큰 capacity를 확보한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 개념적으로:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;capacity 1
&amp;darr;
capacity 2
&amp;darr;
capacity 4
&amp;darr;
capacity 8
&amp;darr;
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 성장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 중요한 점은:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;C++ 표준은 vector의 capacity 성장 배율을 규정하지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체에 따라 1.5배, 2배에 가까운 방식 등 서로 다른 성장 전략을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;vector는 재할당 횟수를 줄이기 위해 일반적으로 현재보다 더 큰 capacity를 확보하며, &lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;구체적인 성장 전략은 표준 라이브러리 구현체에 따라 다르다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;7. vector의 장점&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빠른 Random Access&lt;/h4&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Numbers[Index]; // O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cache Locality가 좋다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소가 연속적으로 존재하므로 순차 탐색 시 CPU Cache를 효율적으로 활용하기 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;메모리 오버헤드가 상대적으로 작다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 각 원소마다 별도의 Node 정보가 필요하지 않다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;vector

[A][B][C][D]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 이중 연결 리스트는 각 노드마다 원소 외에도 연결 정보가 필요하다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;Prev &amp;larr; [A] &amp;rarr; Next
Prev &amp;larr; [B] &amp;rarr; Next
Prev &amp;larr; [C] &amp;rarr; Next&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 원소별 추가 메모리 오버헤드는 일반적으로 vector가 더 작다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 vector는 capacity - size만큼 사용되지 않는 여유 저장 공간을 가지고 있을 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;STL Algorithm과 잘 맞는다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector의 Iterator는 &lt;b&gt;Random Access Iterator&lt;/b&gt;이므로 다양한 STL 알고리즘을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::sort(
    Numbers.begin(),
    Numbers.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;8. vector의 단점&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간 삽입과 삭제가 O(N)이다.&lt;/li&gt;
&lt;li&gt;capacity가 부족하면 &lt;b&gt;reallocation&lt;/b&gt;이 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;reallocation 시 기존 원소를 이동 또는 복사해야 한다.&lt;/li&gt;
&lt;li&gt;reallocation이 발생하면 기존 &lt;b&gt;pointer, reference, iterator가 무효화&lt;/b&gt;된다. ( 이터레이터 무효화 = &lt;span data-token-index=&quot;0&quot;&gt;Iterator Invalidation&lt;/span&gt; )&lt;/li&gt;
&lt;li&gt;실제 필요한 원소보다 capacity가 클 경우 여유 메모리를 보유할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;9. 주요 멤버 함수&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector에서 자주 사용하는 함수들을 기능별로 정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;함수&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;시간 복잡도&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;크기 관련&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;size()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;현재 원소 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;capacity()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;재할당 없이 저장 가능한 원소 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;empty()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;비어 있는지 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;reserve()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt; 가능&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;capacity 미리 확보. &lt;b&gt;원소를 생성하지 않음&lt;/b&gt; &lt;br /&gt;현재 capacity 이하를 요청하면 보통 O(1)&lt;br /&gt;더 크게 늘리면 reallocation으로 O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;resize()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;size 변경. &lt;br /&gt;증가 시 새 원소 생성, reallocation 발생 가능. &lt;br /&gt;감소 시 뒤 원소 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;shrink_to_fit()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt; 가능&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;여분 capacity 축소 요청. &lt;br /&gt;실제 축소가 일어나면 원소 이동/복사 때문에 O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;원소 삽입 / 제거 / 교체&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;push_back()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;Amortized O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;맨 뒤에 원소 삽입. void 반환 &lt;br /&gt;reallocation 발생 시 O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;emplace_back()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;Amortized O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;맨 뒤에서 원소 &lt;b&gt;직접 생성.&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;삽입된 원소에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;reference를 반환&lt;/b&gt; &lt;br /&gt;reallocation 발생 시 O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;pop_back()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;마지막 원소 제거&lt;br /&gt;&lt;b&gt;반환값 없음&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(먼저 값을 얻은 뒤 제거해야 함)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;insert()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;원하는 위치에 삽입. &lt;br /&gt;뒤 원소 이동, reallocation 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;emplace()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;원하는 위치에서 원소 직접 생성. &lt;br /&gt;뒤 원소 이동, reallocation 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;erase()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;특정 위치 또는 범위 삭제. &lt;br /&gt;뒤 원소를 앞으로 이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;clear()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;모든 원소 제거&lt;br /&gt;일반적으로 capacity는 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;assign()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;기존 원소를 제거하고 새 원소들로 교체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;원소 접근&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;front()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;첫 번째 원소 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;back()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;마지막 원소 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;data()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;연속 저장 공간의 시작 포인터 반환&lt;br /&gt;C 스타일 API나 메모리 기반 API와 연결할 때 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;at()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;범위 검사하며 인덱스 접근&lt;br /&gt;&lt;b&gt;잘못된 인덱스&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;라면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;std::out_of_range&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;예외&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;operator[]&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;범위 검사 없이 인덱스 접근&lt;br /&gt;잘못된 인덱스를 사용하면 UB 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.6977%;&quot;&gt;벡터 스왑&lt;/td&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;swap()&lt;/td&gt;
&lt;td style=&quot;width: 18.2559%;&quot;&gt;&lt;b&gt;보통 O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.093%;&quot;&gt;두 vector의 내부 저장소 관리 정보를 교환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;10. reserve()와 resize() 비교&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;10.1 reserve()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 들어올 원소 개수를 알고 있다면 미리 capacity를 확보해 reallocation 횟수를 줄일 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;std::vector&amp;lt;Player&amp;gt; Players;

Players.reserve(1000);

for (int i = 0; i &amp;lt; 1000; ++i)
{
    Players.emplace_back();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;capacity가 충분한 동안은 capacity 부족으로 인한 reallocation이 발생하지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;10.2 resize()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resize()는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;vector의 size를 변경한다.&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers;

Numbers.resize(5); // size = 5&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int의 경우 새 원소들은 value-initialization되어 일반적으로 0이 된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;[0][0][0][0][0]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;capacity가 부족하다면 &lt;b&gt;reallocation이 발생할 수도 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;size는 줄어들지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;capacity까지 자동으로 줄어드는 것은 아니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;기존

[10][20][30][40][50]

resize(3)

[10][20][30]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면,&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;reserve()
&amp;rarr; 저장 공간 확보
&amp;rarr; capacity 변경
&amp;rarr; size는 변경하지 않음

resize()
&amp;rarr; 실제 원소 개수 변경
&amp;rarr; size 변경
&amp;rarr; 필요하면 capacity도 증가할 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. push_back()과 emplace_back() 비교&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;11.1 push_back()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 뒤에 원소를 추가한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;std::string&amp;gt; Names;

std::string Name = &quot;Minsu&quot;;

Names.push_back(Name); // Name이 lvalue이므로 복사&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Names.push_back(std::move(Name)); // 이동 생성 이용하는 방식 : lvalue를 rvalue처럼 취급&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Names.push_back(
    std::string(&quot;Minsu&quot;) // 임시 객체 전달하는 방식
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 객체 : 이름을 붙여 따로 저장하지 않고, 표현식 안에서 잠깐 생성되어 사용된 뒤 사라지는 객체&lt;/p&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;11.2 emplace_back()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emplace_back()은 &lt;b&gt;전달받은 생성자 인자를 이용해 vector의 마지막 위치에 원소를 직접 생성한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;class Player
{
public:
    Player(int Hp, std::string Name)
        : Hp(Hp),
          Name(std::move(Name))
    {
    }

private:
    int Hp;
    std::string Name;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push_back()은 다음처럼 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;Player&amp;gt; Players;

Players.push_back(
    Player(100, &quot;Minsu&quot;) // 임시 객체 생성 후,  vector 내부로 이동
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Players.emplace_back(
    100,
    &quot;Minsu&quot;
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 Player 객체가 아니라 &lt;b&gt;Player 생성자에 필요한 인자&lt;/b&gt;를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;100, &quot;Minsu&quot;
     &amp;darr;
vector 저장 공간에서
Player(100, &quot;Minsu&quot;) 직접 생성&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 경우에 따라 임시 객체 생성과 이동을 줄일 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11.3 emplace_back()이 항상 더 빠른 것은 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 객체 P가 있다면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Players.push_back(P);

또는

Players.push_back(
    std::move(P)
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emplace_back()의 핵심은:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 더 빠르다가 아니라, 생성자 인자를 직접 전달하여 원소를 제자리 생성할 수 있다는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11.4 반환값 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++17 이후:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emplace_back()은 삽입된 원소에 대한 &lt;b&gt;reference를 반환&lt;/b&gt;한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Player&amp;amp; P = Players.emplace_back(
    100,
    &quot;Minsu&quot;
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 push_back()은 &lt;b&gt;void를 반환&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;12. shrink_to_fit() 자세히 보기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shrink_to_fit()은 &lt;b&gt;현재 사용하지 않는 여분의 capacity를 줄여달라고 요청하는 함수&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers;

Numbers.reserve(1000);

for (int i = 0; i &amp;lt; 100; ++i)
{
    Numbers.push_back(i);
}

// size     = 100
// capacity = 1000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.shrink_to_fit();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 호출하면 여분의 capacity를 줄여달라고 구현체에 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현체가 실제로 축소했다면 예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;size     = 100
capacity = 100&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 중요한 점은 &lt;b&gt;&lt;span&gt;shrink_to_fit()은 non-binding request &lt;/span&gt;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 실제로 capacity가 줄어드는 것은 보장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음을 가정하면 안 된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.shrink_to_fit();

assert(
    Numbers.capacity() ==
    Numbers.size()
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;h1&gt;13. Reallocation과 Iterator Invalidation&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;13.1 Reallocation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector의 가장 중요한 내부 동작 중 하나가 &lt;b&gt;Reallocation&lt;/b&gt;이다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;기존

0x1000
┌────┬────┬────┐
│ 10 │ 20 │ 30 │
└────┴────┴────┘

V.push_back(40);
// 기존 저장 영역 바로 뒤가 비어 있다고 보장할 수 없기 때문에 
// vector는 더 큰 새로운 연속 저장 공간을 확보한다.


새 저장 공간

0x5000
┌────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │    │    │
└────┴────┴────┴────┴────┴────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 다음 과정이 일어난다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;새로운 연속 메모리 확보
        &amp;darr;
기존 원소 이동 또는 복사 생성
        &amp;darr;
기존 원소의 lifetime 종료
        &amp;darr;
기존 저장 공간 해제&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정이 Reallocation이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;13.2 Iterator Invalidation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; V;

V.reserve(3);

V.push_back(10);
V.push_back(20);
V.push_back(30);

int* Ptr = &amp;amp;V[0];
int&amp;amp; Ref = V[0];
auto It = V.begin();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 변수 모두 현재 첫 번째 원소를 가리키고 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0x1000

┌────┬────┬────┐
│ 10 │ 20 │ 30 │
└────┴────┴────┘
  &amp;uarr;
 Ptr
 Ref
 It&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;V.push_back(40);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 reallocation이 발생했다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;기존

0x1000
┌────┬────┬────┐
│ 10 │ 20 │ 30 │
└────┴────┴────┘


새 저장 공간

0x5000
┌────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │
└────┴────┴────┴────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 저장 영역에 있던 객체들의 lifetime이 끝났기 때문에:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;*Ptr;
Ref;
*It;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 더 이상 사용하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;Reallocation이 발생하면 기존 원소를 가리키는 모든 pointer, reference, iterator가 무효화된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드도 위험하다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Values = {
    1, 2, 3
};

for (auto It = Values.begin(); It != Values.end(); ++It)
{
    if (*It == 2)
    {
        Values.push_back(100);
        // 위험 : reallocation이 발생하면 현재 사용 중인 It 자체가 무효화될 수 있음
        // 무효화된 It 사용하면 UB
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;13.3 왜 Reference도 무효화되는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reference는 특정 객체에 바인딩된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int&amp;amp; Ref = V[0]; // 현재 존재하는 V[0] 객체를 참조&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 reallocation이 발생하면:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;기존 V[0] 객체 lifetime 종료
        &amp;darr;
새 메모리에 새로운 원소 생성&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 똑같이 10이어도 기존 객체와 새 위치의 객체는 같은 lifetime의 객체가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 기존 reference는 &lt;b&gt;dangling reference&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;13.4 push_back()에서 reallocation이 없으면?&lt;/h2&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;capacity가 충분해서 reallocation이 발생하지 않았다면 기존 원소를 가리키는 iterator/reference/pointer는 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &lt;b&gt;기존의 end iterator는 무효화&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 원소가 추가되면서 새로운 end() 위치가 생기기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 insert(), erase()도 Reallocation이 발생한 경우, 모든 iterator, pointer, reference가 무효화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reallocation이 발생하지 않은 경우에는&amp;nbsp;&lt;b&gt;삽입/삭제 위치 및 그 이후의 iterator/reference/pointer가 무효화된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 위치보다 앞쪽 원소를 가리키는 것들은 유지된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;13.4&lt;span&gt;&amp;nbsp;&lt;/span&gt;Iterator Invalidation 예방&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예상 크기를 알면 reserve() 한다.&lt;/li&gt;
&lt;li&gt;변경 후 다시 iterator/reference를 얻는다&lt;/li&gt;
&lt;li&gt;장기간 식별에는 ID나 Handle 고려한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;[번외] std::array와 std::vector&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::array와 std::vector는 둘 다 원소를 &lt;b&gt;연속된 메모리&lt;/b&gt;에 저장하는 Sequence Container다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 순차 탐색 시&amp;nbsp;&lt;b&gt;공간 지역성이 좋다. (=&lt;span&gt;&amp;nbsp;&lt;/span&gt;Cache Friendly)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 일반적으로 std::list 같은 Node 기반 컨테이너보다 순회 성능이 좋은 경우가 많다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;std::array

[A][B][C][D]


std::vector 원소 저장 영역

[A][B][C][D]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 가장 큰 차이는 &lt;b&gt;크기와 메모리 관리 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff;&quot;&gt;std::array&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff;&quot;&gt;std::vector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;크기&lt;/td&gt;
&lt;td&gt;고정&lt;/td&gt;
&lt;td&gt;&lt;b&gt;동적&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;크기 정보&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;타입에 포함된 compile-time 상수&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;runtime에 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;원소 저장 위치&lt;/td&gt;
&lt;td&gt;객체 내부&lt;/td&gt;
&lt;td&gt;&lt;b&gt;별도의 동적 저장 공간&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;연속 메모리&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;Random Access&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;push_back()&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;resize()&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;Reallocation&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;capacity 부족 시 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;Iterator Invalidation&lt;/td&gt;
&lt;td&gt;구조적 크기 변경 자체가 없음&lt;/td&gt;
&lt;td&gt;삽입/삭제/reallocation에 따라 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;추가 관리 정보&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;매우 작음&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;size/capacity/저장소 관리 정보 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;std::array&lt;/h2&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::array&amp;lt;int, 3&amp;gt; Arr = { 10, 20, 30 };&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 3은 타입의 일부다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 원소 타입, 원소 개수
std::array&amp;lt;int, 3&amp;gt;
std::array&amp;lt;int, 4&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿 인자가 서로 다르기 때문에, &lt;b&gt;서로 다른 타입&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;*C++에서 서로 다른 템플릿 인자로 만들어진 템플릿 specialization은 서로 다른 타입이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소 저장 공간이 &lt;b&gt;array 객체 자체에 포함&lt;/b&gt;되어 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;std::array&amp;lt;int, 3&amp;gt;

┌────┬────┬────┐
│ 10 │ 20 │ 30 │
└────┴────┴────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;std::vector&lt;/h2&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; V;

V.push_back(10);
V.push_back(20);
V.push_back(30);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소는 vector 객체와 &lt;b&gt;별도의 동적 저장 공간&lt;/b&gt;에 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;vector 객체
┌────────────────────┐
│  저장소 관리 정보   │
└──────┬─────────────┘
       │
       ▼

연속 동적 저장 공간
┌────┬────┬────┬────┐
│ 10 │ 20 │ 30 │    │
└────┴────┴────┴────┘&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요에 따라 실행 중 크기를 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::vector는 &lt;b&gt;연속된 메모리를 사용하는 동적 배열 컨테이너&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 특징은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;연속 메모리
    &amp;darr;
Random Access O(1)
    &amp;darr;
좋은 Cache Locality&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;동적 크기
    &amp;darr;
size / capacity 관리
    &amp;darr;
capacity 부족
    &amp;darr;
Reallocation&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 구조를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 vector를 사용할 때는 다음 개념을 함께 이해해야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector는 연속된 메모리를 사용하는 대신, &lt;br /&gt;저장 공간이 변경될 때 기존 원소를 가리키는 iterator, pointer, reference가 무효화될 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 std::array와 비교하면 다음 한 문장으로 정리할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::array는 크기가 타입에 포함된 고정 크기 연속 컨테이너이고, &lt;br /&gt;std::vector는 별도의 동적 저장 공간을 관리하며 실행 중 크기를 변경할 수 있는 연속 컨테이너다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>C++/STL과 표준 라이브러리</category>
      <category>array</category>
      <category>capacity</category>
      <category>emplace_back</category>
      <category>iterator</category>
      <category>pointer</category>
      <category>reference</category>
      <category>stl</category>
      <category>vector</category>
      <category>캐시지역성</category>
      <category>컨테이너</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/61</guid>
      <comments>https://lo88ng.tistory.com/61#entry61comment</comments>
      <pubDate>Sun, 6 Sep 2026 01:05:56 +0900</pubDate>
    </item>
    <item>
      <title>STL(Standard Template Library)이란?</title>
      <link>https://lo88ng.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 vector, list, map, sort 등을 사용하다 보면 자연스럽게 &lt;b&gt;STL(Standard Template Library)&lt;/b&gt;이라는 용어를 접하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL은 단순히 여러 자료구조를 모아놓은 라이브러리가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨테이너에 데이터를 저장하고, Iterator를 통해 접근하며, Algorithm이 Iterator를 기준으로 동작하도록 구성한 범용 라이브러리 설계&lt;/b&gt;라는 점이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 STL이 등장한 배경과 주요 구성 요소를 먼저 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. STL의 등장 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C에서는 연결 리스트, 스택, 큐와 같은 자료구조가 표준 라이브러리의 범용 컨테이너 형태로 제공되지 않기 때문에 필요한 자료구조를 직접 구현하거나 별도의 라이브러리를 사용해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬이나 탐색의 경우 qsort, bsearch와 같은 표준 함수도 존재하지만, 현대 C++의 STL처럼 &lt;b&gt;다양한 자료형과 자료구조에 동일한 형태로 적용할 수 있는 범용적인 구조&lt;/b&gt;는 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 정수용 스택을 직접 구현했다고 가정해 보자.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;struct IntStack
{
    int Data[100]; // 실제 저장할 데이터의 타입
    int Top;       // 스택의 현재 위치를 나타내는 인덱스
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 float을 저장해야 한다면 별도의 자료구조가 필요할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct FloatStack
{
    float Data[100];
    int Top;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료형만 다를 뿐 거의 동일한 코드가 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;구현마다 성능과 안정성이 달라질 수 있다.&lt;/li&gt;
&lt;li&gt;자료구조와 알고리즘이 강하게 결합되기 쉽다.&lt;/li&gt;
&lt;li&gt;유지보수해야 하는 코드의 양이 증가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 이를 해결할 수 있는 &lt;b&gt;Template&lt;/b&gt; 기능을 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;template &amp;lt;typename T&amp;gt;
struct Stack
{
    T Data[100]; // 실제 저장할 데이터의 타입
    int Top;     // 스택의 현재 위치를 나타내는 인덱스
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 구현으로 여러 자료형을 처리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Stack&amp;lt;int&amp;gt; IntStack;
Stack&amp;lt;float&amp;gt; FloatStack;
Stack&amp;lt;double&amp;gt; DoubleStack;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL은 이러한 &lt;b&gt;Generic Programming&lt;/b&gt; 개념을 바탕으로 컨테이너, Iterator, 알고리즘 등을 서로 조합해서 사용할 수 있도록 설계되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;*Generic Programming : 데이터 형식에 의존하지 않고 알고리즘이나 자료구조를 일반화하여 재사용성을 높이는 프로그래밍 방식&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;2. STL이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;STL(Standard Template Library)&lt;/b&gt;은 템플릿을 기반으로 컨테이너, Iterator, 알고리즘 등 범용적으로 재사용할 수 있는 구성 요소를 제공하는 라이브러리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날에는 STL의 구성 요소들이 &lt;b&gt;C++ 표준 라이브러리(Standard Library)&lt;/b&gt;에 포함되어 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지 구분할 필요가 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL과 C++ Standard Library는 완전히 같은 의미가 아니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 표준 라이브러리는 STL보다 더 넓은 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 기능도 C++ 표준 라이브러리에 포함된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::string
std::thread
std::filesystem
std::chrono
std::iostream
std::unique_ptr&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 개발 환경에서는 vector, map, iterator, sort 등의 &lt;b&gt;컨테이너와 알고리즘 계열을 설명할 때 STL이라는 표현을 관습적으로 많이 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL의 핵심 구성 요소는 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구성 요소&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;대표 예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Container&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;데이터를 저장하고 관리한다.&lt;/td&gt;
&lt;td&gt;vector, list, deque, map, set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Iterator&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컨테이너의 원소를 탐색하고 접근한다.&lt;/td&gt;
&lt;td&gt;iterator, const_iterator, reverse_iterator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Algorithm&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Iterator 범위의 데이터를 처리한다.&lt;/td&gt;
&lt;td&gt;sort, find, count, copy, transform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Function Object&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;알고리즘 등에 동작이나 조건을 전달한다.&lt;/td&gt;
&lt;td&gt;std::less, std::greater, 사용자 정의 Functor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Allocator&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컨테이너가 사용할 메모리 할당 정책을 제공한다.&lt;/td&gt;
&lt;td&gt;std::allocator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;3. STL의 주요 구성 요소&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.1 Container&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Container는 여러 데이터를 저장하고 관리하기 위한 객체이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너마다 내부 자료구조와 특성이 다르기 때문에 필요한 연산에 따라 적절한 컨테이너를 선택해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL 컨테이너는 일반적으로 크게 세 종류로 구분할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;STL
├─ Container // 여기!
│  ├─ Sequence Container
│  │  ├─ vector
│  │  ├─ deque
│  │  └─ list
│  │
│  ├─ Associative Container
│  │  ├─ map
│  │  └─ set
│  │
│  ├─ Unordered Associative Container
│  │  ├─ unordered_map
│  │  └─ unordered_set
│  │
│  └─ Container Adapter // 성격이 다름
│     ├─ stack
│     ├─ queue
│     └─ priority_queue
│
├─ Iterator
├─ Algorithm
├─ Function Object
└─ Allocator&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sequence Container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소의 &lt;b&gt;순서 자체가 중요한 컨테이너&lt;/b&gt;이다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::vector
std::deque
std::list
std::forward_list
std::array&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Associative Container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key를 기준으로 정렬된 상태를 유지하는 컨테이너이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 내부적으로 &lt;b&gt;균형 이진 탐색 트리 계열&lt;/b&gt;의 자료구조를 사용해 구현된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::map
std::set
std::multimap
std::multiset&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준은 특정 내부 구현을 직접 강제하지는 않지만, 요구되는 복잡도를 만족하기 위해 대부분의 구현체가 Red-Black Tree와 같은 균형 트리를 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unordered Associative Container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블을 기반으로 Key를 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::unordered_map
std::unordered_set
std::unordered_multimap
std::unordered_multiset&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균적으로 검색, 삽입, 삭제를 O(1)에 수행할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.2 Container Adapter&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack, queue, priority_queue는 일반적인 Container와 약간 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::stack
std::queue
std::priority_queue&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 직접 데이터를 저장하기 위한 독립적인 자료구조라기보다 &lt;b&gt;기존 컨테이너의 인터페이스를 제한하여 특정 자료구조처럼 사용할 수 있도록 만든 Container Adapter&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 std::stack과 std::queue는 기본적으로 std::deque를 내부 컨테이너로 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::stack&amp;lt;int&amp;gt; Stack;

// 개념적으로는 아래와 같다.
std::stack&amp;lt;int, std::deque&amp;lt;int&amp;gt;&amp;gt; Stack;

// 필요하면 다른 컨테이너를 지정할 수 있다.
std::stack&amp;lt;int, std::vector&amp;lt;int&amp;gt;&amp;gt; Stack;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;4. Iterator&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Iterator는 컨테이너의 원소를 탐색하고 접근하기 위한 객체이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터와 비슷하게 다음 연산을 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;*It
++It
It != End&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 vector의 원소를 Iterator로 순회할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers = {1, 2, 3};

for (auto It = Numbers.begin(); It != Numbers.end(); ++It)
{
    std::cout &amp;lt;&amp;lt; *It &amp;lt;&amp;lt; '\n';
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1
2
3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역방향 Iterator도 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;for (auto It = Numbers.rbegin(); It != Numbers.rend(); ++It)
{
    std::cout &amp;lt;&amp;lt; *It &amp;lt;&amp;lt; '\n';
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3
2
1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Iterator가 중요한 이유는 단순히 반복문을 편하게 작성하기 위해서가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너와 알고리즘을 분리하기 위해 사용한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 std::find는 vector 자체를 인자로 받지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::find(Numbers.begin(), Numbers.end(), 10);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 다음과 같이 &lt;b&gt;Iterator 범위&lt;/b&gt;를 받는다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;[begin, end)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 알고리즘은 컨테이너의 실제 내부 구조를 직접 알 필요가 없다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Container
    &amp;darr;
 Iterator
    &amp;darr;
 Algorithm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 하나의 알고리즘을 여러 컨테이너에 재사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Vector = {1, 2, 3};
std::list&amp;lt;int&amp;gt; List = {1, 2, 3};

std::find(Vector.begin(), Vector.end(), 2);
std::find(List.begin(), List.end(), 2);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vector와 list의 내부 자료구조는 완전히 다르지만 std::find는 동일한 형태로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 STL의 중요한 설계 특징 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;5. Algorithm&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;STL Algorithm은 Iterator가 나타내는 범위의 데이터를 처리하는 범용 함수이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 &amp;lt;algorithm&amp;gt; 헤더에서 다양한 알고리즘을 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 정렬하기
std::sort(Numbers.begin(), Numbers.end());&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 원소 찾기
auto It = std::find(
    Numbers.begin(),
    Numbers.end(),
    10
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 특정 값의 개수 세기
int Count = std::count(
    Numbers.begin(),
    Numbers.end(),
    5
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 복사하기
std::copy(
    Source.begin(),
    Source.end(),
    Destination.begin()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 대부분의 알고리즘이 &lt;b&gt;컨테이너가 아닌 이터레이터&lt;/b&gt;를 사용한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 알고리즘과 자료구조가 강하게 결합되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &lt;b&gt;모든 알고리즘을 모든 컨테이너에서 사용할 수 있는 것은 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘마다 요구하는 Iterator의 종류가 다르기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예가 std::sort이다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::sort(
    Numbers.begin(),
    Numbers.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::sort는 &lt;b&gt;Random Access Iterator&lt;/b&gt;를 요구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 vector에는 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers;

std::sort(
    Numbers.begin(),
    Numbers.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 list에는 사용할 수 없다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;std::list&amp;lt;int&amp;gt; Numbers;

// 컴파일 오류
std::sort(
    Numbers.begin(),
    Numbers.end()
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::list의 Iterator는 Random Access Iterator가 아니기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 list는 자체 정렬 함수를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Numbers.sort();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 STL에서는 &lt;b&gt;알고리즘이 요구하는 Iterator Category와 컨테이너가 제공하는 Iterator Category의 관계&lt;/b&gt;도 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Iterator의 종류는 이후 별도의 글에서 자세히 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;6. Function Object(Functor)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Function Object는 함수처럼 호출할 수 있는 객체이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 클래스나 구조체에서 함수 호출 연산자인&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;operator()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 오버로딩하여 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 Functor를 만들 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct Compare
{
    bool operator()(int A, int B) const
    {
        return A &amp;gt; B;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성한 뒤 함수처럼 호출할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Compare Comp;

bool Result = Comp(10, 5);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::sort의 비교 함수로도 전달할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::sort(
    Numbers.begin(),
    Numbers.end(),
    Compare{} // 소괄호도 가능
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL에서는 기본적으로 여러 Function Object를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::less&amp;lt;int&amp;gt;
std::greater&amp;lt;int&amp;gt;
std::plus&amp;lt;int&amp;gt;
std::minus&amp;lt;int&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 내림차순 정렬을 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::sort(
    Numbers.begin(),
    Numbers.end(),
    std::greater&amp;lt;int&amp;gt;{}
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lambda도 Function Object인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 Lambda를 전달할 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::sort(
    Numbers.begin(),
    Numbers.end(),
    [](int A, int B)
    {
        return A &amp;gt; B;
    }
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lambda 자체는 람다 표현식(Lambda Expression)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 각 Lambda Expression에 대해 고유한 &lt;b&gt;Closure Type&lt;/b&gt;을 만들고, 람다 표현식을 평가하면 해당 타입의 &lt;b&gt;Closure Object&lt;/b&gt;가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체에는 operator()가 정의되므로 함수처럼 호출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 비슷하게 생각할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct CompilerGeneratedLambda
{
    bool operator()(int A, int B) const
    {
        return A &amp;gt; B;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Lambda가 만들어내는 Closure Object 역시 &lt;b&gt;Function Object의 한 종류로 사용할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;7. Allocator&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Allocator는 컨테이너가 원소를 저장할 메모리를 확보하고 반환하는 방법을 추상화한 객체이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 std::vector&amp;lt;int&amp;gt;는 기본 Allocator로 std::allocator&amp;lt;int&amp;gt;를 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;
    int,
    std::allocator&amp;lt;int&amp;gt;
&amp;gt; Numbers;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 Template 인자를 생략하면 기본값이 사용되므로 일반적으로는 다음과 같이 작성한다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Allocator를 별도의 정책으로 분리한 이유는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨테이너의 자료구조 관리&lt;/b&gt;와 &lt;b&gt;메모리 할당 정책&lt;/b&gt;을 분리하기 위해서이다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 보면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::vector
    │
    ├─ 원소 배치와 크기 관리
    │
    └─ Allocator
          └─ 저장 공간 확보 / 반환&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 필요한 경우 컨테이너의 동작 자체를 다시 구현하지 않고 메모리 할당 전략을 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;Arena Allocator&lt;/li&gt;
&lt;li&gt;고정 크기 메모리 영역&lt;/li&gt;
&lt;li&gt;별도의 메모리 추적 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등과 연결된 Allocator를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 게임이나 실시간 애플리케이션에서는 &lt;b&gt;메모리 단편화, allocation 비용, 캐시 효율, 메모리 추적&lt;/b&gt; 등이 중요하기 때문에 메모리 할당 전략이 성능에 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 일상적인 C++ 코드에서는 대부분 기본 Allocator를 그대로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 모든 컨테이너가 동적 메모리 할당을 사용하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::array&amp;lt;int, 10&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은 고정 크기 배열이기 때문에 std::allocator를 사용해 별도의 동적 저장 공간을 확보하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;모든 STL 컨테이너가 std::allocator를 사용한다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 표현하기보다는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 메모리를 관리하는 표준 컨테이너들은 일반적으로 Allocator를 통해 메모리 할당 정책을 지정할 수 있으며, 기본적으로 std::allocator를 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 이해하는 편이 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;8. STL의 핵심 설계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL에서 가장 중요하게 볼 부분은 각각의 기능보다 &lt;b&gt;구성 요소가 어떻게 분리되어 있는가&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&amp;lt;int&amp;gt; Numbers = {5, 3, 1, 4, 2};

std::sort(
    Numbers.begin(),
    Numbers.end(),
    std::greater&amp;lt;int&amp;gt;{} // 내림차순
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 다섯 가지 구성 요소가 모두 사용된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;             ┌─ Function Object (Functor)
             │
Container &amp;rarr; Iterator &amp;rarr; Algorithm
    │
    └─ Allocator&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 STL의 핵심은&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Container가 데이터를 저장하고, Iterator가 데이터에 접근하는 공통 인터페이스 역할을 하며, Algorithm은 Iterator를 통해 데이터를 처리한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 구조에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 덕분에 컨테이너와 알고리즘을 서로 독립적으로 설계하면서도 다양한 조합으로 재사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STL의 핵심 구성 요소를 정리하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;요소&lt;/td&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Container&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;데이터를 저장하고 관리한다.&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Container Adapter : &lt;span style=&quot;background-color: #f9f9f9; text-align: start;&quot;&gt;기존 컨테이너의 인터페이스를 제한해 특정 자료구조를 제공한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Iterator&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컨테이너의 원소에 접근하고 순회한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Algorithm&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Iterator 범위의 데이터를 처리한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Function Object&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;알고리즘 등에 동작과 조건을 전달한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Allocator&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컨테이너의 메모리 할당 정책을 제공한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서도&amp;nbsp;&lt;b&gt;알고리즘이 특정 컨테이너에 직접 의존하지 않고 Iterator를 통해 동작하도록 설계되어 있다는 점&lt;/b&gt;이 STL의 핵심적인 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 실제로 가장 많이 사용되는 STL 컨테이너 중 하나인&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::vector&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부터 내부 구조와 메모리 관리 방식을 자세히 살펴본다.&lt;/p&gt;</description>
      <category>C++/STL과 표준 라이브러리</category>
      <category>algorithm</category>
      <category>allocator</category>
      <category>container</category>
      <category>container adapter</category>
      <category>function object</category>
      <category>iterator</category>
      <category>stl</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/60</guid>
      <comments>https://lo88ng.tistory.com/60#entry60comment</comments>
      <pubDate>Sat, 5 Sep 2026 17:04:46 +0900</pubDate>
    </item>
    <item>
      <title>Spline 기반 3요소 조향 알고리즘</title>
      <link>https://lo88ng.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자동차가 Spline 위의 한 점만 바라보게 하면 경로를 따라 움직일 수는 있다. 하지만 속도가 높아지거나 커브가 급해지면 차량이 도로 중심을 크게 벗어나거나 조향이 늦게 시작되는 문제가 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 줄이기 위해 전방 목표 위치, Spline의 진행 방향, 도로 중심선 이탈 거리를 함께 사용하는 조향 로직을 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;USplineFollowerComponent에서 사용한 Position Delta, Heading Delta, Crosstrack Error의 계산 과정과 각 요소를 결합한 이유를 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1_정상.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pxiD4/dJMcafA4OD0/TIKWEzFmb74lv70Ley5mkk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pxiD4/dJMcafA4OD0/TIKWEzFmb74lv70Ley5mkk/img.gif&quot; data-alt=&quot;Position, Heading, Crosstrack과 속도&amp;amp;middot;곡률 적응형 Look Ahead를 모두 적용한 정상 주행 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pxiD4/dJMcafA4OD0/TIKWEzFmb74lv70Ley5mkk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/pxiD4/dJMcafA4OD0/TIKWEzFmb74lv70Ley5mkk/img.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;375&quot; data-filename=&quot;1_정상.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Position, Heading, Crosstrack과 속도&amp;middot;곡률 적응형 Look Ahead를 모두 적용한 정상 주행 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 GIF는 이 글에서 설명할 세 가지 조향 요소를 모두 적용한 최종 상태다. 차량은 전방 목표점을 따라가면서 Spline의 진행 방향을 미리 반영하고, 중심선에서 이탈하면 Crosstrack 보정으로 복귀한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GIF의 디버그 표시 읽는 법&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;파란 구&lt;/span&gt;는 차량의 현재 진행 거리에 해당하는 Spline 기준 위치다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;빨간 구&lt;/span&gt;는 조향 계산에 실제로 사용하는 Look Ahead 목표점이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;노란 선&lt;/span&gt;은 차량에서 빨간 목표점으로 향하는 방향이다. Position Delta의 기준이 된다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;초록 화살표&lt;/span&gt;는 빨간 목표점에서 Spline이 향하는 접선 방향이다. Heading Delta의 기준이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;조향 계산의 전체 흐름&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조향값은 다음 순서로 계산한다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;차량 위치와 속도 조회&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;속도와 곡률로 &lt;b&gt;Look Ahead&lt;/b&gt; 거리 계산&amp;nbsp; &amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;Spline의 전방 위치와 접선 방향 조회&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;Position Delta 계산&lt;br /&gt;Heading Delta 계산&lt;br /&gt;Crosstrack Error 계산&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;Position과 Heading 가중 결합&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;Crosstrack 보정 추가&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;-1부터 1 사이로 Clamp&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&lt;br /&gt;Chaos Vehicle Steering Input&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Position Delta와 Heading Delta는 각도 오차다. 두 값을 가중 결합해 기본 조향 방향을 구한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Crosstrack Error는 각도가 아니라 도로 중심선에서 벗어난 거리다. 따라서 두 각도와 동일한 가중평균에 넣지 않고 별도의 중심선 복귀 보정으로 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;고정 Look Ahead의 한계&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Look Ahead는 차량이 현재 위치보다 얼마나 앞의 경로(빨간 구)를 바라볼 것인지 결정하는 거리다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고정된 Look Ahead를 사용하면 모든 상황에 같은 거리를 적용하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mGq9T/dJMcad4fSwb/2k7eHh3Qq6DdyW8QXSHw20/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mGq9T/dJMcad4fSwb/2k7eHh3Qq6DdyW8QXSHw20/img.gif&quot; style=&quot;width: 49.2201%; margin-right: 10px;&quot; data-widthpercent=&quot;49.8&quot; data-filename=&quot;7_긴 고정 Look Ahead.gif&quot; data-origin-height=&quot;375&quot; data-origin-width=&quot;400&quot; data-is-animation=&quot;true&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mGq9T/dJMcad4fSwb/2k7eHh3Qq6DdyW8QXSHw20/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmGq9T%2FdJMcad4fSwb%2F2k7eHh3Qq6DdyW8QXSHw20%2Fimg.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWh3cg/dJMcagGPUWR/8ug4xP4wzEXyBORm6r2Rn0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWh3cg/dJMcagGPUWR/8ug4xP4wzEXyBORm6r2Rn0/img.gif&quot; style=&quot;width: 49.6171%;&quot; data-widthpercent=&quot;50.2&quot; data-filename=&quot;6_짧은 고정 Look Ahead.gif&quot; data-origin-height=&quot;372&quot; data-origin-width=&quot;400&quot; data-is-animation=&quot;true&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWh3cg/dJMcagGPUWR/8ug4xP4wzEXyBORm6r2Rn0/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWh3cg%2FdJMcagGPUWR%2F8ug4xP4wzEXyBORm6r2Rn0%2Fimg.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;(좌) Fixed Long Look Ahead (우) Fixed Short Look Ahead&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;좌측은 Look Ahead를 길게 고정한 결과다. 빨간 구가 멀리 있어 직선에서는 조향이 완만하지만 급커브에서는 차량이 현재 도로의 굴곡보다 먼 목표점을 먼저 바라본다. 그 결과 노란 선이 커브 안쪽을 가로지르고 차량도 중심선을 크게 잘라 주행할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우측은 Look Ahead를 매우 짧게 설정한 극단적인 사례다. 빨간 구가 차량 가까이에 붙기 때문에 작은 위치 변화도 목표 방향을 크게 바꾸고 조향이 민감해진다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;GIF 속 커브길에 적절한 Look Ahead 값을 찾았다 해도, 차량의 속도과 커브의 정도에 따라 주행 결과가 또 어긋난다. &lt;/span&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;/li&gt;
&lt;li&gt;급커브에서 너무 멀리 보면 커브 안쪽을 가로지르기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이는 빨간 구가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;동일한&lt;/span&gt; Spline 진행 거리 기준 항상 동일한 거리 앞에 놓이게 되면&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;모든 차량 속도와 도로 형상을 동시에 만족시키기 어렵다는 것을 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;속도와 곡률에 따라 Look Ahead 조절하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 다양한 상황에 대응하기 위해,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;차량 속도&lt;/b&gt;가 높을수록 Look Ahead를 늘리고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;현재 곡률&lt;/b&gt;이 커질수록 다시 줄였다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;CurvatureScale = Lerp(
    1.0,
    0.5,
    Clamp(CurrentCurvature &amp;times; 3.0, 0.0, 1.0)
)

LookAheadDistance =
    (LookAheadBase + Speed &amp;times; LookAheadSpeedFactor)
    &amp;times; CurvatureScale&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직선에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;CurvatureScale이 1에 가까워진다. 차량 속도에 따라 더 먼 지점을 바라보며 조향 변화가 완만해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커브가 급해질수록 Scale은 0.5에 가까워진다. 목표점을 차량과 가까운 위치로 당겨 경로의 굴곡을 더 적극적으로 따라가게 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3_Heading까지 제거.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SA5WW/dJMcaiLfOjc/2kRnpnEKwkYv4DSEzIvvKK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SA5WW/dJMcaiLfOjc/2kRnpnEKwkYv4DSEzIvvKK/img.gif&quot; data-alt=&quot;Speed + Curvature Adaptive Look Ahead&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SA5WW/dJMcaiLfOjc/2kRnpnEKwkYv4DSEzIvvKK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/SA5WW/dJMcaiLfOjc/2kRnpnEKwkYv4DSEzIvvKK/img.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;373&quot; data-filename=&quot;3_Heading까지 제거.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Speed + Curvature Adaptive Look Ahead&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 조금은 어색한 부분이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차량 앞 방향과 노란선이 벌어지는 양상을 보면, 차량이 Spline이 꺾이는 방향을 늦게 반영하고 있는 것을 볼 수 있다. 그래서 중심선을 비스듬하게 슬라이딩하는 모습까지 보이게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 바로 &lt;b&gt;목표점 하나만 추종했을 때의 문제&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Position Delta&lt;/span&gt; / Heading Delta / Crosstrack Error&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d; text-align: start;&quot;&gt;*현재까지는 이 Position Delta만 적용되고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spline 추종을 구현하는 가장 단순한 방법은 차량 앞의 목표점을 구하고 그 방향으로 조향하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;차량 현재 위치
    &amp;darr;
Spline 위 전방 목표점 (빨간 구)
    &amp;darr;
목표점 방향과 차량 방향의 각도 차이
    &amp;darr;
Steering Input&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Position Delta는 차량이 전방 목표 위치를 바라보기 위해 필요한 &lt;u&gt;Yaw 각도 차이&lt;/u&gt;다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;전방 목표 위치로 가려면
현재 방향에서 얼마나 회전해야 하는가?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 현재 진행 거리에 Look Ahead를 더해 전방 목표 위치를 구한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 열린 Spline에서는 목표 거리가 전체 길이를 넘지 않도록 제한한다. 
// 닫힌 Spline에서는 전체 길이를 기준으로 Wrap한다.

LookAheadProgress = ProgressDistance + LookAheadDistance

LookAheadPosition =
    Spline.GetLocationAtDistanceAlongSpline(
        LookAheadProgress,
        WorldSpace
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차량 위치에서 목표 위치로 향하는 방향은 다음과 같이 구한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;DirectionToTarget =
    Normalize(LookAheadPosition - VehicleLocation)

TargetYaw = atan2(
    DirectionToTarget.Y,
    DirectionToTarget.X
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 차량 Yaw와 목표 방향의 가장 짧은 각도 차이를 계산한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;PositionDelta =
    FindDeltaAngleDegrees(VehicleYaw, TargetYaw)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Position Delta / &lt;span style=&quot;color: #000000;&quot;&gt;Heading Delta&lt;/span&gt; /&lt;span&gt;&amp;nbsp;&lt;/span&gt;Crosstrack Error&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Position Delta는 목표점의 위치를 보지만 그 지점에서 도로가 어느 방향으로 이어지는지는 알지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heading Delta는 Look Ahead 지점의 Spline 접선 방향&lt;span style=&quot;color: #409d00;&quot;&gt;(초록 화살표)&lt;/span&gt;과 차량 방향의 차이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;전방의 도로는 어느 방향으로 이어지며
차량은 그 방향과 얼마나 어긋나 있는가?&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;PathDirection =
    Spline.GetDirectionAtDistanceAlongSpline(
        LookAheadProgress,
        WorldSpace
    )

PathYaw = atan2(
    PathDirection.Y,
    PathDirection.X
)

HeadingDelta =
    FindDeltaAngleDegrees(VehicleYaw, PathYaw)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Position Delta가 목표 위치를 향하게 만든다면 Heading Delta는 전방 경로의 방향을 미리 조향에 반영한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 값은 비슷해 보이지만 역할이 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Position Delta는 목표점으로 수렴하게 한다.&lt;/li&gt;
&lt;li&gt;Heading Delta는 Spline의 진행 방향에 차량의 자세를 맞춘다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blNJPA/dJMcabFnh0i/St8RGWPiLCIvEPHdzgi6X0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blNJPA/dJMcabFnh0i/St8RGWPiLCIvEPHdzgi6X0/img.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;374&quot; data-filename=&quot;2_Crosstrack 기능만 제거.gif&quot; data-is-animation=&quot;true&quot; data-widthpercent=&quot;45.46&quot; style=&quot;width: 44.9349%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blNJPA/dJMcabFnh0i/St8RGWPiLCIvEPHdzgi6X0/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblNJPA%2FdJMcabFnh0i%2FSt8RGWPiLCIvEPHdzgi6X0%2Fimg.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ouq48/dJMcag0XVM2/CGzpjqVQ7muLx1Wvwnj0qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ouq48/dJMcag0XVM2/CGzpjqVQ7muLx1Wvwnj0qK/img.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;622&quot; data-is-animation=&quot;false&quot; width=&quot;463&quot; height=&quot;361&quot; style=&quot;width: 53.9023%;&quot; data-widthpercent=&quot;54.54&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ouq48/dJMcag0XVM2/CGzpjqVQ7muLx1Wvwnj0qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fouq48%2FdJMcag0XVM2%2FCGzpjqVQ7muLx1Wvwnj0qK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Position + Heading&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Position에 Heading을 추가한 결과다. 빨간 구의 위치는 같지만 초록 화살표가 나타내는 Spline 접선 방향을 실제 조향에 함께 사용한다. 따라서 단순히 목표점을 향하는 것을 넘어 전방에서 도로가 꺾이는 방향으로 차량 자세를 미리 맞춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차량 자세를 맞추면서 중앙선에서 벗어나는 현상은 다음으로 나오는 Crosstrack Error로 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Position Delta / Heading Delta /&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Crosstrack Error&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Position과 Heading을 함께 사용해도 차량이 도로 중심에서 벗어난 상태로 경로와 평행하게 달릴 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8_Crosstrack 별도 검증_없음.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0DMUc/dJMcacLcPa6/YBifuyVtBgXhEbyy40ouS0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0DMUc/dJMcacLcPa6/YBifuyVtBgXhEbyy40ouS0/img.gif&quot; data-alt=&quot;중심선에서 약간 이탈시켜 출발한 차량, Position + Heading&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0DMUc/dJMcacLcPa6/YBifuyVtBgXhEbyy40ouS0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/0DMUc/dJMcacLcPa6/YBifuyVtBgXhEbyy40ouS0/img.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;372&quot; data-filename=&quot;8_Crosstrack 별도 검증_없음.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중심선에서 약간 이탈시켜 출발한 차량, Position + Heading&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Crosstrack Error는 차량과 Spline 중심선 사이의 좌우 이탈 거리를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 현재 진행 거리에서 Spline의 위치와 접선 방향을 구한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;NearestOnPath =
    Spline.GetLocationAtDistanceAlongSpline(
        ProgressDistance,
        WorldSpace
    )

PathTangent = Normalize(NearestOnPath)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 차량 위치와 중심선 위치의 Offset을 계산한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Offset = VehicleLocation - NearestOnPath&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Offset에는 Spline의 진행 방향 성분도 포함될 수 있다. 좌우 이탈만 얻기 위해 접선 방향 성분을 제거한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Offset에서 접선 방향 성분을 제거해 수직 이탈을 구하는 과정
// 주황 화살표 - 초록 화살표

PerpendicularOffset = Offset - PathTangent &amp;times; Dot(Offset, PathTangent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3utd4/dJMcac5ptTe/IJ4qrHfurrXfdvPtFTUvP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3utd4/dJMcac5ptTe/IJ4qrHfurrXfdvPtFTUvP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3utd4/dJMcac5ptTe/IJ4qrHfurrXfdvPtFTUvP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3utd4%2FdJMcac5ptTe%2FIJ4qrHfurrXfdvPtFTUvP1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;329&quot; height=&quot;358&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;849&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;월드 UpVector만 사용하지 않은 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평평한 도로라면 월드 &lt;b&gt;UpVector&lt;/b&gt;와 &lt;b&gt;Path Tangent&lt;/b&gt;의 Cross Product로 &lt;b&gt;도로 오른쪽 방향&lt;/b&gt;을 구할 수 있다. (왼손 좌표계)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 경사로나 높낮이가 있는 도로에서는 월드 수평면만 기준으로 계산하면 좌우 방향에 불필요한 오차가 생길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 줄이기 위해 월드 UpVector를 Path Tangent에 수직인 평면으로 투영해 도로 기준의 법선 방향을 만들었다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;ProjectedUp =
    WorldUp
    - PathTangent &amp;times; Dot(WorldUp, PathTangent)

RoadNormal = Normalize(ProjectedUp)

PathRight = Cross(RoadNormal, PathTangent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 수직 Offset을 Path Right에 투영한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;CrosstrackError =
    Dot(PerpendicularOffset, PathRight)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtDAQd/dJMcajclsz5/CBfOLrndDczqbGsrIxsFn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtDAQd/dJMcajclsz5/CBfOLrndDczqbGsrIxsFn0/img.png&quot; data-alt=&quot;경사로와 평지를 달리는 차량 우측 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtDAQd/dJMcajclsz5/CBfOLrndDczqbGsrIxsFn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtDAQd%2FdJMcajclsz5%2FCBfOLrndDczqbGsrIxsFn0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;315&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;경사로와 평지를 달리는 차량 우측 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽으로 이탈했다면 왼쪽으로 조향하고, 왼쪽으로 이탈했다면 오른쪽으로 조향해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Crosstrack 보정 후&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8_Crosstrack 별도 검증_있음.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4t8u6/dJMcadXzcOk/jm5qgo4HnoZdWA3AxIJOt0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4t8u6/dJMcadXzcOk/jm5qgo4HnoZdWA3AxIJOt0/img.gif&quot; data-alt=&quot;Position + Heading + Crosstrack 차량이 중심선으로 돌아온다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4t8u6/dJMcadXzcOk/jm5qgo4HnoZdWA3AxIJOt0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/4t8u6/dJMcadXzcOk/jm5qgo4HnoZdWA3AxIJOt0/img.gif&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;373&quot; data-filename=&quot;8_Crosstrack 별도 검증_있음.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Position + Heading + Crosstrack 차량이 중심선으로 돌아온다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 중심선에서 이탈한 상태로 시작해도, Crosstrack Error의 부호와 크기가 최종 Steering에 추가되면서 차량이 파란 구가 이동하는 중심선 방향으로 더 명시적으로 복귀한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세 요소 결합하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Position Delta와 Heading Delta는&lt;span&gt;&amp;nbsp;&lt;/span&gt;HeadingWeight로 결합한다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;YawCommand =
    PositionDelta &amp;times; (1 - HeadingWeight)
    + HeadingDelta &amp;times; HeadingWeight&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값에서는 Heading의 비중을 높여 전방 경로 방향을 미리 반영한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 커브가 급할 때는 Position의 비중을 높인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 위치로의 수렴을 강화해 커브에서 경로를 크게 벗어나는 것을 줄이기 위해서다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;DynamicHeadingWeight = Lerp(
    HeadingWeight,
    0.3,
    Clamp(CurrentCurvature &amp;times; 3.0, 0.0, 1.0)
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 Steering은 Yaw 명령을 정규화하고 Crosstrack 보정을 빼서 계산한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Steering = Clamp(
    YawCommand / MaxYawDelta
    - CrosstrackError &amp;times; CrosstrackGain,
    -1.0,
    1.0
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 이탈은 양수이므로 Steering에서 양수 보정값을 뺀다. 결과적으로 차량은 왼쪽으로 조향한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 왼쪽 이탈은 음수다. 음수를 빼면 Steering 값이 증가해 오른쪽 복귀 조향이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 요소를 결합하면서 각 요소가 서로 다른 문제를 담당하게 됐다. 목표 위치만 추종할 때보다 커브 방향을 먼저 반영할 수 있었고, 중심선에서 벗어난 뒤에도 복귀 방향을 명시적으로 계산할 수 있었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Position Delta&lt;/h4&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;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Heading Delta&lt;/h4&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;차량 자세를 Spline 접선 방향에 맞춘다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Crosstrack Error&lt;/h4&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;계산한 값을 Chaos Vehicle에 전달하기&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;USplineFollowerComponent는&lt;span&gt;&amp;nbsp;&lt;/span&gt;소유 Pawn의 공개 인터페이스를 호출하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;최종 Steering 값을 넘긴다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;OwnerPawn-&amp;gt;DoSteering(Steering);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ACityVehiclePawn은 전달받은 값을 Chaos Vehicle Movement Component에 적용한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;void ACityVehiclePawn::DoSteering(float SteeringValue)
{
    ChaosVehicleMovement-&amp;gt;SetSteeringInput(
        SteeringValue
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 SplineFollowerComponent는 차량 물리 컴포넌트의 세부사항보다 조향 계산에 집중할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;기술 기록 시리즈&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;Unreal Engine 5로 만든 자동차 자율주행 시뮬레이션&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;DataTable과 SceneCapture2D로 구현한 인터랙티브 차량 선택 UI&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;UI 인터랙션을 위한 세 가지 Material 기록&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;&lt;b&gt;Spline 기반 3요소 조향 알고리즘&lt;/b&gt; &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;(현재 게시글)&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;곡률 기반 안전속도와 계층형 속도 제한&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;Blackboard와 Behavior Tree를 이용한 환경 대응&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal; color: #000000;&quot;&gt;구현 과정에서 발견한 문제와 개선 방향&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>프로젝트/자동차 자율주행 시뮬레이션</category>
      <category>c++</category>
      <category>ChaosVehicle</category>
      <category>spline</category>
      <category>ue5</category>
      <category>UnrealEngine5</category>
      <category>게임개발</category>
      <category>기술블로그</category>
      <category>자율주행</category>
      <category>조향알고리즘</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/59</guid>
      <comments>https://lo88ng.tistory.com/59#entry59comment</comments>
      <pubDate>Sat, 29 Aug 2026 01:21:15 +0900</pubDate>
    </item>
    <item>
      <title>C++ static 정리 &amp;mdash; 정적 지역 변수부터 정적 멤버 변수와 함수까지</title>
      <link>https://lo88ng.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 static을 처음 배우면 조금 혼란스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 안에서도 사용할 수 있고, 전역 변수에도 붙일 수 있으며, 클래스의 멤버 변수와 멤버 함수에도 붙일 수 있기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;static int Value;

class Cat
{
    static int Count;
    static void PrintCount();
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 같은 static이라는 키워드를 사용하지만 의미가 완전히 동일한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 static = 프로그램이 끝날 때까지 살아 있는 변수라고만 기억하면 파일 범위 static이나 정적 멤버 함수를 설명하기 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번에는 static을 &lt;b&gt;어디에 사용했는가&lt;/b&gt;를 기준으로 나누어 정리해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수의 수명과 범위&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 이해할 때는 &lt;b&gt;수명(storage duration)&lt;/b&gt;과 &lt;b&gt;범위(scope)&lt;/b&gt;를 구분해서 보는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C에서 공부했던 내용을 단순화하면 다음처럼 볼 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;종류&lt;/td&gt;
&lt;td&gt;수명&lt;/td&gt;
&lt;td&gt;접근 가능한 범위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반 지역 변수&lt;/td&gt;
&lt;td&gt;함수 실행 동안&lt;/td&gt;
&lt;td&gt;해당 블록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정적 지역 변수&lt;/td&gt;
&lt;td&gt;프로그램 실행 동안&lt;/td&gt;
&lt;td&gt;해당 블록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전역 변수&lt;/td&gt;
&lt;td&gt;프로그램 실행 동안&lt;/td&gt;
&lt;td&gt;선언을 볼 수 있는 영역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일 범위 static 변수&lt;/td&gt;
&lt;td&gt;프로그램 실행 동안&lt;/td&gt;
&lt;td&gt;해당 번역 단위&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 &lt;b&gt;수명과 범위는 서로 다른 개념&lt;/b&gt;이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 정적 지역 변수는 함수가 끝난 뒤에도 객체 자체는 살아 있지만, 그 이름은 함수 밖에서 사용할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Foo()
{
    static int Count = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count의 수명은 길지만 scope는 여전히 Foo() 내부다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수 내부의 static&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 내부에서 static 지역 변수를 선언하면 해당 객체는 &lt;b&gt;static storage duration&lt;/b&gt;, 즉 정적 저장 기간을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;선언과 동시에 초기화하면, 초기화는 한 번만 이루어진다!&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;함수 내부의 정적 지역 변수는 해당 선언에 처음 도달했을 때 초기화되고, 이후 함수가 다시 호출되어도 다시 초기화되지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;C++11부터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;함수 지역 정적 변수의 초기화가 스레드 안전하게 한 번만 수행되도록 언어 차원에서 보장&lt;/b&gt;된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

void CreateCanCoke()
{
    static int CreatedCanCokeCount = 0; // 선언과 동시에 초기화

    ++CreatedCanCokeCount;

    std::cout
        &amp;lt;&amp;lt; &quot;CreatedCanCokeCount: &quot;
        &amp;lt;&amp;lt; CreatedCanCokeCount
        &amp;lt;&amp;lt; '\n';
}

int main()
{
    CreateCanCoke(); //1
    CreateCanCoke(); //2
    CreateCanCoke(); //3
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;CreatedCanCokeCount: 1
CreatedCanCokeCount: 2
CreatedCanCokeCount: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 지역 변수였다면 함수가 호출될 때마다 새로 만들어졌을 것이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void CreateCanCoke()
{
    int CreatedCanCokeCount = 0;

    ++CreatedCanCokeCount;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 호출할 때마다 다시 0에서 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(+) 선언과 동시에 초기화 하지 않으면, 이후 코드가 매번 실행되어 의도한 동작이 안 될 수도 있다는 점 참고!&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

void CreateCanCoke()
{
    static int CreatedCanCokeCount; // 선언과 동시에 초기화 하기 않으면
    
    CreatedCanCokeCount = 0;
    
    ++CreatedCanCokeCount;

    std::cout
        &amp;lt;&amp;lt; &quot;CreatedCanCokeCount: &quot;
        &amp;lt;&amp;lt; CreatedCanCokeCount
        &amp;lt;&amp;lt; '\n';
}

int main()
{
    CreateCanCoke(); // 1
    CreateCanCoke(); // 1
    CreateCanCoke(); // 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 섹션이라고 외우지 않기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 변수에 대해 흔히&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 변수는 데이터 영역에 저장된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;static의 본질은 &quot;데이터 영역에 저장된다&quot;가 아니라 &quot;정적 저장 기간을 가진다&quot;는 것이다.&lt;/b&gt;&lt;br /&gt;.data나 .bss에 저장되는 것은 일반적인 컴파일 환경에서 그 규칙을 구현하는 대표적인 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 개념적으로는&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;일반 지역 변수
&amp;rarr; 함수 호출 동안 살아 있음
&amp;rarr; automatic storage duration
&amp;rarr; 일반적으로 스택을 사용

static 지역 변수
&amp;rarr; 프로그램 종료까지 살아 있음
&amp;rarr; static storage duration
&amp;rarr; 일반적으로 .data / .bss 등의 정적 영역 사용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 이해하는 것이 더 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스의 static 멤버 변수 - 정적 멤버 변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 안에서도 멤버 변수에 static을 붙일 수 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;class Cat
{
public:
    Cat();

private:
    static float TailLength;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 멤버 변수는 객체마다 하나씩 존재한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Cat
{
    float Weight;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Cat A;
Cat B;
Cat C;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라면 Weight는 각각의 객체 A, B, C가 자기 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 정적 멤버 변수는 다르다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Cat
{
    static float TailLength;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cat 객체가 몇 개 만들어지든 TailLength는 객체마다 복제되는 멤버가 아니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Cat A
Cat B
Cat C

        &amp;darr;

Cat::TailLength 하나를 공유
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정적 멤버 변수는 &lt;b&gt;특정 객체가 아니라 클래스 자체에 속하는 상태&lt;/b&gt;를 표현할 때 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 지금까지 생성된 객체 수를 기록할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Cat
{
public:
    Cat();

    static int GetCount();

private:
    static int Count;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;int Cat::Count = 0;

Cat::Cat()
{
    ++Count;
}

int Cat::GetCount()
{
    return Count;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;Cat A;
Cat B;
Cat C;

std::cout &amp;lt;&amp;lt; Cat::GetCount();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 3이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정적 멤버 변수의 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 C++ 코드에서는 클래스 안에 정적 멤버 변수를 선언한 뒤 cpp 파일에서 별도로 정의한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Cat.h

#pragma once

class Cat
{
private:
    static float TailLength; // 정적 멤버의 선언
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Cat.cpp

#include &quot;Cat.h&quot;

float Cat::TailLength = 5.0f;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 안의&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;static float TailLength;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 정적 멤버의 선언이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 정의는&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;float Cat::TailLength = 5.0f;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 클래스 밖에 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;C++17에서는 inline static도 사용할 수 있다&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 멤버 변수는 반드시 cpp 파일에서 초기화해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 외우면 현대 C++에서는 정확하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++17부터는 inline static 멤버를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Cat
{
private:
    inline static float TailLength = 5.0f;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 헤더에서 바로 정의하고 초기화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 cpp 파일에서 이 헤더를 include해도 inline 변수에 대한 ODR 규칙 덕분에 중복 정의 문제가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음 두 방식을 구분해두면 된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 전통적인 방식

// Cat.h
class Cat
{
    static float TailLength; // 선언만 - 헤더에서 초기화할 경우 중복 정의 에러 발생
};

// Cat.cpp
float Cat::TailLength = 5.0f;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 C++17 이후에는&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Cat
{
    // inline static 멤버로 헤더에서 바로 정의하고 초기화 가능
    inline static float TailLength = 5.0f;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;생성자에서 정적 멤버를 초기화하지 않는 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 멤버 변수는 일반 멤버 변수와 달리 &lt;b&gt;생성자 초기화 리스트&lt;/b&gt;에서 초기화하는 대상이 아니다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Cat.h

class Cat
{
public:
    Cat(float InWeight)
        : Weight(InWeight) // 초기화
    {
    	TailLength = 5; // 대입 - 문법적으로 가능
                        // 하지만 객체 생성 때마다 공유 변수에 대입됨
    }

private:
    float Weight;
    static float TailLength;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Weight는 각각의 Cat 객체가 자기 값을 가지므로 객체 생성 과정에서 초기화하는 것이 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체마다 만들어지는 값이 아니기 때문에 객체 생성자의 멤버 초기화 리스트에서 초기화하는 개념과 맞지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;(생성자 초기화 리스트는 기본적으로 &lt;b&gt;지금 생성 중인 객체의 구성 요소(subobject)를 초기화하는 곳&lt;/b&gt;이다.)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 본문에서 정적 멤버에 값을 대입하는 것은 가능하지만, 이는 &lt;b&gt;초기화가 아니라&lt;/b&gt; 객체가 생성될 때마다 수행되는 일반적인 &lt;b&gt;대입&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Cat.cpp

float Cat::TailLength = 5.0f; // 반드시 Cat::&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 밖에서 초기화해줄 경우, 반드시 Cat:: 을 붙여 초기화해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 Cat의 정적 멤버를 정의하는 것이 아니라 &lt;b&gt;별개의 전역 변수&lt;/b&gt;를 정의하는 것이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;static과 접근 지정자는 별개의 개념이다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1787805082964&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

class Cat
{
public:
    // static 이라고 해서 자동으로 클래스 내부에서만 접근 가능한 것은 아니다.
    inline static int PublicCount = 0;

private:
    inline static int CatCount = 5;
};

int main()
{
    // public static 변수 - 클래스 외부에서도 직접 접근하고 수정 가능
    Cat::PublicCount = 10;
    
    // private static 변수
    //Cat::CatCount = 10; // 컴파일 에러
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;static 멤버 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버 함수에도 static을 붙일 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Math
{
public:
    static float Square(float Value)
    {
        return Value * Value;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[특징 1] 정적 멤버 함수는 &lt;span style=&quot;color: #ee2323;&quot;&gt;객체가 없어도 호출할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;float Result = Math::Square(12.0f);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[특징 2] 정적 멤버 함수에는 특정 객체가 없으므로 &lt;span style=&quot;color: #ee2323;&quot;&gt;this 포인터도 없다. &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 멤버 함수의 예시를 보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

class Cat
{
public:
    Cat(int InAge)
        : Age(InAge)
    {
    }

    void PrintAge()
    {
        std::cout &amp;lt;&amp;lt; this-&amp;gt;Age &amp;lt;&amp;lt; '\n';
    }

private:
    int Age;
};

int main()
{
    Cat CatA(3);

    CatA.PrintAge(); // 3

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrintAge() 내부의 this는 개념적으로 &amp;amp;CatA 를 가리킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정적 멤버 함수에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;특정 객체가 없으므로 this 포인터도 없다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1787801980857&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Cat
{
public:
    static void StaticFunction()
    {
        // std::cout &amp;lt;&amp;lt; this-&amp;gt;Age; // ❌ this가 없음
    }

private:
    int Age = 3;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정적 멤버 함수는 &lt;b&gt;특정 객체의 상태와 관계없이 클래스라는 논리적 범위에 묶고 싶은 기능&lt;/b&gt;을 표현할 때 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;static 멤버 함수가 일반 멤버 변수에 접근할 수 없는 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 클래스를 생각해보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Cat
{
public:
    void NormalFunction();
    static void StaticFunction();

private:
    int Age = 3;
    static int Count;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 멤버 함수에서는 둘 다 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void Cat::NormalFunction()
{
    Age = 5;
    Count = 10;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 가능할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 멤버 함수에는 &lt;b&gt;this가 있기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;this-&amp;gt;Age
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 객체의 Age인지 명확하다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(특정 가능)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 멤버 변수 Count 역시 클래스 전체에 하나뿐이므로 접근할 수 있다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(특정 가능)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;반면 정적 멤버 함수는 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void Cat::StaticFunction()
{
    Age = 5;   // 오류
    Count = 10; // 가능
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StaticFunction()에는 this가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Age = 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 작성하면 컴파일러 입장에서는 이런 문제가 생긴다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;어느 Cat의 Age를 말하는 것인가?

A.Age?
B.Age?
C.Age?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Count는 클래스 전체에 하나다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;Cat::Count
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 어떤 객체를 선택할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 코드&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 GetCatCount()도 static인가?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 CatCount는 특정 Cat 객체의 상태가 아니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 굳이 특정 객체를 통해 물어볼 필요 없이, 클래스 자체에게 현재 고양이가 몇 마리인지 묻는 것이 의미상 자연스럽다.&lt;/p&gt;
&lt;pre id=&quot;code_1787805047922&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

class Cat
{
public:
    // static 이라고 해서 자동으로 클래스 내부에서만 접근 가능한 것은 아니다.
    inline static int PublicCount = 0;
    
    static int GetCatCount()
    {
        return CatCount; // 클래스 내부니까 Cat:: 없어도 됨
    }

private:
    inline static int CatCount = 5;
};



int main()
{
    // public static 변수 - 클래스 외부에서도 직접 접근하고 수정 가능
    Cat::PublicCount = 10;
    ++Cat::PublicCount; 
    cout &amp;lt;&amp;lt; Cat::PublicCount &amp;lt;&amp;lt; &quot;\n&quot;; // 11
    
    // private static 변수
    //Cat::CatCount = 10; // 컴파일 에러
    
    int temp = Cat::GetCatCount();
    cout &amp;lt;&amp;lt; temp&amp;lt;&amp;lt; &quot;\n&quot;; // 5
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멤버와 static 멤버의 접근 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;호출 위치&lt;/td&gt;
&lt;td&gt;일반 멤버 변수&lt;/td&gt;
&lt;td&gt;static 멤버 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반 멤버 함수&lt;/td&gt;
&lt;td&gt;해당 객체(this)의 멤버 변수이므로 특정 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;가능&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클래스 단위로 한 개만 존재하므로 특정 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;가능&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;static 멤버 함수&lt;/td&gt;
&lt;td&gt;직접 접근 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;불가능&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 어떤 객체를 선택할 필요가 없기 때문에 &lt;/span&gt;&lt;b&gt;가능&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 static이라는 문법 자체보다 &lt;b&gt;현재 함수에 특정 객체를 나타내는 this가 존재하는가&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;일반 멤버 함수
&amp;rarr; this 있음
&amp;rarr; 특정 객체의 일반 멤버 접근 가능

static 멤버 함수
&amp;rarr; this 없음
&amp;rarr; 특정 객체의 일반 멤버를 알 수 없음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 정적 멤버 함수에 객체를 직접 전달한다면 그 객체의 멤버에는 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Cat
{
public:
    static void PrintAge(const Cat&amp;amp; Target)
    {
        std::cout &amp;lt;&amp;lt; Target.Age;
    }

private:
    int Age = 3;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 정적 멤버 함수가 멤버에 절대 접근할 수 없다는 것이 아니라, &lt;b&gt;암시적인 대상 객체인 this가 없다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;static 함수만 모은 클래스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 특정 객체를 만들 필요가 없는 함수들을 하나의 클래스에 모으는 코드도 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class MyMath
{
public:
    static float Square(float Value)
    {
        return Value * Value;
    }

    static float Power(float Value, float Exponent)
    {
        return std::pow(Value, Exponent);
    }

private:
    MyMath() = delete;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때는 객체를 만들지 않는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;float A = MyMath::Square(3.0f);
float B = MyMath::Power(2.0f, 3.0f);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 클래스는 사실상 &lt;b&gt;클래스 이름을 namespace처럼 사용하여 관련 함수를 묶은 형태&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 C++에서는 객체 상태가 전혀 필요 없는 단순 함수 그룹이라면 클래스만이 정답은 아니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;namespace MyMath
{
    float Square(float Value)
    {
        return Value * Value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 namespace를 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;객체의 개념이 필요한가?
클래스의 private 상태와 함께 관리되어야 하는가? 
단순한 함수 그룹인가?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 보고 클래스와 namespace&amp;nbsp; 선택하는 것이 더 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-size: 1.25em;&quot;&gt;static 멤버 함수의 필요성&lt;/span&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div data-is-intersecting=&quot;true&quot; data-turn-id-container=&quot;request-WEB:b693c506-5b41-400f-9c0c-474c64bf2456-13&quot;&gt;
&lt;div&gt;
&lt;div data-conversation-screenshot-content=&quot;&quot;&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-5-6-thinking&quot; data-turn-start-message=&quot;true&quot; data-message-id=&quot;639a9288-2300-45e1-bba3-bec4446a065f&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;3389&quot; data-start=&quot;3238&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체는 필요 없지만 클래스의 캡슐화 경계 안에 있어야 할 때&lt;/b&gt; 정적 멤버 함수가 특히 의미가 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&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-end=&quot;3130&quot; data-start=&quot;3095&quot; data-section-id=&quot;e8kiur&quot;&gt;Player::Create() 같은 private 생성자를 사용하는 &lt;b&gt;Factory&lt;/b&gt; (객체 없이 생성 필요한 경우)&lt;/li&gt;
&lt;li data-end=&quot;3184&quot; data-start=&quot;3131&quot; data-section-id=&quot;lf49sw&quot;&gt;GameManager::GetInstance() 같은 &lt;b&gt;Singleton 접근 함수&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;3229&quot; data-start=&quot;3185&quot; data-section-id=&quot;lklkaq&quot;&gt;Enemy::GetCount() 같은 &lt;b&gt;클래스 공유 상태 접근 함수&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static은 사용되는 위치에 따라 의미가 달라진다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;함수 내부 static
&amp;rarr; 함수가 끝나도 객체의 수명을 유지한다.

파일 범위 static
&amp;rarr; 이름을 해당 번역 단위 내부로 제한한다.

static 멤버 변수
&amp;rarr; 객체마다 존재하지 않고 클래스 단위로 공유한다.

static 멤버 함수
&amp;rarr; 특정 객체 없이 호출하며 this 포인터가 없다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 static을 단순히 &quot;오래 살아 있는 변수&quot; 정도로 이해했는데, 사용 위치별로 나누어보니 &lt;b&gt;수명, 링키지, 객체 소속 여부라는 서로 다른 문제를 하나의 키워드가 다루고 있다는 점&lt;/b&gt;이 핵심이었다.&lt;/p&gt;</description>
      <category>C++/C++ 문법과 핵심 개념</category>
      <category>c++</category>
      <category>static</category>
      <category>storage duration</category>
      <category>this 포인터</category>
      <category>정적멤버변수</category>
      <category>정적멤버함수</category>
      <category>정적변수</category>
      <category>정적지역변수</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/58</guid>
      <comments>https://lo88ng.tistory.com/58#entry58comment</comments>
      <pubDate>Thu, 27 Aug 2026 13:33:55 +0900</pubDate>
    </item>
    <item>
      <title>C++ extern과 파일 범위 static &amp;mdash; 다른 cpp 파일의 변수는 어떻게 연결될까?</title>
      <link>https://lo88ng.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 프로젝트가 커지면 하나의 소스 파일에 모든 코드를 작성하지 않고 여러 .h, .cpp 파일로 나누게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 한 가지 궁금증이 생긴다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// A.cpp
int GValue = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A.cpp에 만들어 둔 GValue를 B.cpp에서도 사용하려면 어떻게 해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 어떤 전역 변수는 &lt;b&gt;해당 cpp 파일 안에서만 사용하고 외부에서는 접근하지 못하게 만들고 싶을 수도 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 등장하는 것이 extern과 파일 범위에서 사용하는 static이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 키워드를 이해하려면 단순히 &quot;전역 변수를 공유한다&quot;, &quot;접근을 막는다&quot;라고 외우기보다 &lt;b&gt;선언과 정의&lt;/b&gt;, 그리고 &lt;b&gt;링키지(linkage)&lt;/b&gt;를 함께 이해하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선언과 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 extern을 이해하려면 선언과 정의의 차이를 알아야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선언&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언은 컴파일러에게 이름과 타입이 존재한다는 사실을 알려주는 것이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int GValue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 대략 다음과 같은 의미다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GValue라는 int 변수가 어딘가에 정의되어 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의는 &lt;b&gt;실제 객체를 만들어&lt;/b&gt; 저장 공간을 갖게 하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int GValue = 2;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 실제 GValue 객체를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;extern int GValue;  // 선언
int GValue = 2;     // 정의
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 구분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;extern&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extern은 다른 번역 단위(translation unit)에 정의된 이름을 사용할 때 주로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 번역 단위는 간단하게 생각하면 &lt;b&gt;전처리가 끝난 하나의 cpp 파일&lt;/b&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;1363&quot; data-origin-height=&quot;535&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baktPD/dJMcabyBaq7/ANQek11LdMpmA3xQ9uvpF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baktPD/dJMcabyBaq7/ANQek11LdMpmA3xQ9uvpF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baktPD/dJMcabyBaq7/ANQek11LdMpmA3xQ9uvpF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaktPD%2FdJMcabyBaq7%2FANQek11LdMpmA3xQ9uvpF1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;1363&quot; height=&quot;535&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;1363&quot; data-origin-height=&quot;535&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같이 구성해볼 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ExternTest.h&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#pragma once

extern int GValue;

void IncreaseValue();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더에서는 GValue를 정의하지 않고 선언만 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 cpp 파일에서 이 헤더를 include해도 각 파일에는 다음 선언만 들어간다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int GValue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 여러 개의 GValue가 만들어지는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ExternTest.cpp&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &quot;ExternTest.h&quot;

int GValue = 2; // 실제 변수 정의 - extern 필요 없음

void IncreaseValue()
{
    ++GValue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 변수의 정의는 하나의 cpp 파일에 둔다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int GValue = 2;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 실제 GValue가 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Main.cpp&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &quot;ExternTest.h&quot;
	// 이 부분에 extern int GValue; void IncreaseValue(); 가 적히게 됨. 
	// 즉, 어딘가에 GValue가 있을 거라는 심볼(빈칸). 링커 과정에서 채워짐. 

int main()
{
    std::cout &amp;lt;&amp;lt; GValue &amp;lt;&amp;lt; '\n';

    IncreaseValue();

    std::cout &amp;lt;&amp;lt; GValue &amp;lt;&amp;lt; '\n';

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main.cpp 입장에서는 GValue의 실제 정의가 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 헤더에&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int GValue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 있기 때문에 컴파일러는 GValue라는 이름이 다른 곳에 정의되어 있다고 가정하고 코드를 컴파일할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 링커가 각각의 번역 단위를 연결하면서 Main.cpp에서 사용한 GValue와 ExternTest.cpp에서 정의한 GValue를 연결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 extern은 변수를 만드는 키워드라기보다 &lt;b&gt;다른 곳에 존재하는 정의를 참조하기 위한 선언&lt;/b&gt;이라고 이해하는 편이 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파일 범위 static&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extern이 다른 번역 단위에서도 이름을 사용할 수 있도록 하는 것과 반대로, 파일 범위의 static은 이름을 현재 번역 단위 내부로 제한하는 데 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// A.cpp

static int GValue = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 GValue는 A.cpp 내부에서만 연결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;internal linkage&lt;/b&gt;, 즉 내부 링키지를 가진다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다른 파일에서 접근하려 하면&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 코드가 있다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// Globals.h

extern int GValue;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// A.cpp

static int GValue = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// B.cpp

#include &quot;Globals.h&quot;
#include &amp;lt;iostream&amp;gt;

void PrintValue()
{
    std::cout &amp;lt;&amp;lt; GValue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B.cpp는 헤더를 통해 다음 선언을 본다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int GValue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;컴파일 자체는 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;링크 단계에서는 문제&lt;/b&gt;가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B.cpp가 기대하는 것은 외부에서 연결할 수 있는 GValue의 정의다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 A.cpp의&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;static int GValue = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 A.cpp 내부에서만 사용할 수 있는 이름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 링커는 B.cpp가 요구하는 GValue의 정의를 찾지 못하고 링크 오류가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;B.cpp
extern int GValue
        &amp;darr;
외부에서 GValue를 찾음

A.cpp
static int GValue = 10
        &amp;darr;
A.cpp 내부에서만 보임

        &amp;darr;

연결할 정의 없음
&amp;rarr; 링크 오류
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;extern과 static의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 범위에서 두 키워드의 차이를 정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;코드&lt;/td&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;int Value = 10;&lt;/td&gt;
&lt;td&gt;전역 변수 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;extern int Value;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;다른 곳에 정의된 Value를 사용하겠다는 선언&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;static int Value = 10;&lt;/td&gt;
&lt;td&gt;현재 번역 단위 내부에서만 연결 가능한 전역 변수 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 파일 범위의 static에서 중요한 것은 단순히 &quot;접근 제한&quot;이라는 표현보다 &lt;b&gt;internal linkage를 부여한다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++의 static은 사용 위치에 따라 의미가 달라지기 때문에 이것을 구분해두는 것이 좋다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;파일 범위 static
&amp;rarr; 이름의 링키지를 현재 번역 단위로 제한

함수 내부 static
&amp;rarr; 함수 호출이 끝나도 객체의 수명을 유지

클래스 static 멤버
&amp;rarr; 특정 객체가 아니라 클래스 자체에 속하는 멤버
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 static이지만 각각 강조되는 성질이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파일 내부에서만 사용할 값이라면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 cpp 파일 내부에서만 사용할 전역 변수나 함수를 만들 때 파일 범위 static을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;static int InternalValue = 10;

static void PrintInternalValue()
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 현대 C++에서는 같은 목적으로 &lt;b&gt;익명 namespace&lt;/b&gt;도 많이 사용한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;namespace
{
    int InternalValue = 10;

    void PrintInternalValue()
    {
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 안에 선언한 이름 역시 해당 번역 단위 내부에서만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 C 스타일의 파일 범위 static을 만났을 때는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 이름은 이 cpp 파일 내부 구현에만 사용하려는 것이구나.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 이해하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extern과 파일 범위의 static은 여러 cpp 파일로 프로그램을 나누기 시작하면 자연스럽게 만나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extern은&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern int Value;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 &lt;b&gt;다른 곳에 정의가 존재한다고 선언&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 파일 범위의 static은&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;static int Value = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 해당 이름에 &lt;b&gt;내부 링키지&lt;/b&gt;를 부여하여 현재 번역 단위 내부에서만 연결되도록 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 두 개념은 다음처럼 대비해서 기억할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;extern
&amp;rarr; 다른 번역 단위에 있는 정의와 연결하고 싶다.

파일 범위 static
&amp;rarr; 이 이름을 현재 번역 단위 안에 숨기고 싶다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C++/C++ 문법과 핵심 개념</category>
      <category>c++</category>
      <category>extern</category>
      <category>external linkage</category>
      <category>internal linkage</category>
      <category>static</category>
      <category>링키지</category>
      <category>번역단위</category>
      <category>전역변수</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/57</guid>
      <comments>https://lo88ng.tistory.com/57#entry57comment</comments>
      <pubDate>Thu, 27 Aug 2026 11:51:44 +0900</pubDate>
    </item>
    <item>
      <title>C++ 동적 메모리 할당은 왜 필요할까? - Stack과 Heap</title>
      <link>https://lo88ng.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에서 사용하는 모든 데이터의 크기와 수명을 프로그램 실행 전에 미리 알 수 있는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게임을 만든다고 생각해보자. 사용자가 선택한 플레이어 수, 런타임에 생성되는 몬스터의 수, 불러온 맵 데이터의 크기처럼 &lt;b&gt;프로그램이 실행된 이후에야 필요한 메모리 크기가 결정되는 경우&lt;/b&gt;가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 어떤 객체는 특정 함수가 끝난 뒤에도 계속 살아 있어야 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 데이터를 다루려면 단순히 지역 변수를 선언하는 것만으로는 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;왜 동적 메모리 할당이 필요한지&lt;/b&gt;, 그리고 이를 이해하기 위해 필요한 &lt;b&gt;Stack과 Heap 메모리의 차이&lt;/b&gt;를 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동적 메모리 할당이 필요한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[1] 정적 크기만으로는 모든 상황을 처리할 수 없다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 배열을 선언했다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int Players[10];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 배열은 최대 10개의 int를 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 실제로 플레이어가 몇 명 필요한지 프로그램을 실행하기 전에는 알 수 없는 경우다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int Count;
cin &amp;gt;&amp;gt; Count;

int Players[Count]; // 컴파일 에러 - expression did not evaluate to a constant&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;플레이어 수
&amp;rarr; 사용자 입력에 따라 결정

몬스터 수
&amp;rarr; 게임 진행 상황에 따라 결정

맵 데이터 크기
&amp;rarr; 런타임에 불러온 데이터에 따라 결정
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 필요한 데이터의 크기가 &lt;b&gt;런타임(Runtime)&lt;/b&gt; 에 결정된다면 처음부터 고정된 크기의 변수만 사용하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 예상되는 최대 크기만큼 미리 크게 잡을 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int Players[10000];
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로 몇 개가 필요할지도 모르는 상태에서 항상 최대 크기를 확보하는 방식은 불필요한 메모리를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 만큼의 메모리를 실행 중에 확보할 수 있다면 더 유연하게 데이터를 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 &lt;b&gt;동적 메모리 할당(Dynamic Memory Allocation)&lt;/b&gt; 이 필요한 첫 번째 이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[2] 객체의 수명을 스코프와 분리해야 할 수도 있다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 메모리가 필요한 이유는 크기뿐만이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 변수는 자신이 선언된 스코프와 수명이 연결되어 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void CreatePlayer()
{
    int HP = 100;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HP는 CreatePlayer()가 실행되는 동안 존재하고, 함수가 끝나면 더 이상 사용할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CreatePlayer() 호출
        &amp;darr;
HP 생성
        &amp;darr;
함수 실행
        &amp;darr;
CreatePlayer() 종료
        &amp;darr;
HP 수명 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 프로그램에서는 객체를 함수가 끝난 뒤에도 유지해야 하는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 함수에서 플레이어를 생성했지만, 해당 플레이어는 게임이 끝날 때까지 계속 사용해야 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 필요한 것은&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;원하는 시점에 생성
        &amp;darr;
필요한 동안 유지
        &amp;darr;
원하는 시점에 해제
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할 수 있는 메모리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;객체의 생명주기를 특정 스코프와 분리해야 하는 경우&lt;/b&gt;에도 동적 메모리를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Memory&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 지역 변수나 매개변수, 리턴값 등은 일반적으로 &lt;b&gt;Stack Memory&lt;/b&gt;와 밀접하게 관련되어 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int Test(int Damage)
{
    int HP = 100;
    HP -= Damage;
    return HP;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Damage, HP와 같은 값은 함수 실행과 함께 사용되고 함수가 끝나면 해당 호출에 속한 스택 영역도 정리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack의 특징을 간단히 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Stack Memory

주로 저장되는 것
&amp;rarr; 지역 변수, 매개변수, 리턴값 
&amp;rarr; 함수 호출과 관련된 데이터

수명
&amp;rarr; 해당 함수/스코프의 실행과 밀접하게 연결

관리
&amp;rarr; 함수 호출과 반환에 따라 자동으로 처리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 직접&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;delete HP;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 작업을 할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 종료되면 해당 함수 호출이 사용하던 스택 영역이 자동으로 정리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 스택은 사용하기 편하고 메모리 관리 비용도 비교적 작다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap Memory&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 프로그램 실행 중에 필요한 만큼 메모리를 동적으로 확보할 수 있는 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 대표적으로 new를 사용해 Heap에 메모리를 할당할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* Ptr = new int;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;Stack                   Heap

Ptr ──────────────────► [ int ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 Ptr과 Ptr이 가리키는 객체가 서로 다른 위치에 존재할 수 있다는 것이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int* Ptr = new int;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 지역 포인터 변수 Ptr 자체는 현재 함수의 지역 변수이므로 Stack 쪽에서 관리될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 new int로 생성된 int 객체는 Heap에 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Ptr
&amp;rarr; Heap 객체의 주소를 저장

Heap 객체
&amp;rarr; 실제 동적으로 할당된 데이터
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Heap 객체의 수명은 스코프와 다르다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap에 동적으로 할당한 객체는 해당 포인터 변수가 스코프를 벗어난다고 자동으로 해제되는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Heap을 이용하면 객체를 생성한 함수의 스코프와 객체의 생명주기를 분리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Character* CreateCharacter()
{
    Character* Player = new Character();
    return Player;
}

int main()
{
    Character* Player = CreateCharacter();

    Player-&amp;gt;Move();

    delete Player;
    Player = nullptr;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1535&quot; data-start=&quot;1417&quot; data-ke-size=&quot;size16&quot;&gt;CreateCharacter() 함수는 이미 종료되었지만, Heap에 동적으로 생성된 Character 객체의 수명은 종료되지 않는다. 동적으로 할당된 객체는 명시적으로 해제하기 전까지 유지되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;할당 시점
    &amp;darr;
필요한 동안 사용
    &amp;darr;
해제 시점
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 메모리의 수명을 보다 유연하게 관리할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그렇다면 Heap이 Stack보다 좋은 것일까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지는 않다. Heap은 유연한 대신 관리 비용이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 할당 요청이 발생하면 메모리 관리자는 사용 가능한 영역을 찾아 적절한 메모리 블록을 할당해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Stack은 함수 호출과 반환에 따라 일정한 방식으로 메모리를 관리할 수 있기 때문에 일반적으로 할당과 해제가 더 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Stack
&amp;rarr; 빠르고 관리가 단순함

Heap
&amp;rarr; 유연하지만 관리 비용이 더 큼
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap을 사용한다고 해서 항상 성능이 나쁜 것은 아니지만, &lt;b&gt;동적 할당이 공짜는 아니라는 점&lt;/b&gt;은 기억해둘 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 짧은 시간 동안 수많은 할당과 해제가 반복되는 프로그램에서는 동적 할당 비용도 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Heap은 개발자가 수명을 관리해야 한다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap의 더 큰 문제는 &lt;b&gt;메모리 관리 책임&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 메모리를 할당했다고 해보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* Ptr = new int(100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용이 끝났는데도 해제하지 않으면 해당 메모리가 계속 할당된 상태로 남을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int* Ptr = new int(100);

// delete Ptr; 누락
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 더 이상 필요하지 않은 동적 메모리가 적절하게 반환되지 않는 문제를 &lt;b&gt;Memory Leak, 메모리 누수&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 해당 메모리를 가리키던 포인터까지 잃어버리면 문제가 더 커진다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int* Ptr = new int(100);

Ptr = nullptr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 처음 할당한 메모리는 여전히 Heap에 존재하지만 주소를 잃어버렸기 때문에 정상적으로 해제할 방법도 사라졌다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Heap

[100]
 &amp;uarr;
가리키는 포인터가 없음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 동적 메모리를 사용할 때는 단순히&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;어디서 할당할 것인가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 생각할 것이 아니라&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;누가 이 메모리를 소유하는가?
언제까지 필요한가?
어디서 해제할 것인가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;까지 함께 생각해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로그램의 메모리 영역&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 메모리를 공부하면서 Stack과 Heap 외에도 프로그램이 사용하는 메모리 영역을 함께 정리해두면 이해하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 다음과 같이 생각할 수 있다. 각 영역의 역할을 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Code
Data
Heap
Stack&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Code 영역&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴파일된 프로그램의 실행 코드&lt;/b&gt;, 즉 CPU가 실행할 기계어 명령 등이 저장되는 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 함수 자체의 실행 명령이 여기에 포함될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Move()
{
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;프로그램이 &quot;무엇을 실행해야 하는지&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 대한 코드가 저장되는 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Data 영역&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 변수와 정적 변수처럼 &lt;b&gt;프로그램 실행 기간 동안 유지되는 데이터&lt;/b&gt;가 저장되는 영역이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int GlobalValue = 100;

void Test()
{
    static int Count = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 변수의 수명은 일반적으로 프로그램의 실행 기간과 함께한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;프로그램 시작
    &amp;darr;
전역/정적 객체 존재
    &amp;darr;
프로그램 실행
    &amp;darr;
프로그램 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부적으로는 초기화 여부 등에 따라 여러 섹션으로 다시 나뉠 수 있지만, 우선은 전역 변수와 정적 변수가 프로그램 수명 동안 유지되는 영역이라고 이해해두면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Stack 영역&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출 과정에서 필요한 데이터가 관리되는 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 지역 변수, 매개변수, 리턴값고 같이 함수 호출과 관련된 정보가 여기에 위치할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void Attack(int Damage)
{
    int Result = Damage * 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출과 반환에 따라 자동으로 관리된다는 것이 중요한 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Heap 영역&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 실행 중 동적으로 요청한 메모리를 관리하는 영역이다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Character* Player = new Character();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 객체는 Stack의 지역 변수처럼 함수가 끝났다고 자동으로 없어지는 것이 아니라, 해당 자원의 소유자가 적절하게 수명을 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stack과 Heap 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;중요한 것은 어느 한쪽이 항상 더 좋은 것이 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터의 크기와 수명에 맞는 저장 방식을 선택하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구분&lt;/td&gt;
&lt;td&gt;Stack&lt;/td&gt;
&lt;td&gt;Heap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 용도&lt;/td&gt;
&lt;td&gt;지역 변수, 함수 호출 관련 데이터&lt;/td&gt;
&lt;td&gt;동적으로 할당한 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;크기 결정&lt;/td&gt;
&lt;td&gt;일반적으로 함수 실행 구조 및 정적 타입 정보에 따라 관리&lt;/td&gt;
&lt;td&gt;런타임에 필요한 크기만큼 요청 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수명&lt;/td&gt;
&lt;td&gt;스코프 및 함수 호출과 밀접하게 연결&lt;/td&gt;
&lt;td&gt;할당부터 해제까지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관리&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;동적 자원의 소유자가 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;할당/해제 비용&lt;/td&gt;
&lt;td&gt;일반적으로 작음&lt;/td&gt;
&lt;td&gt;일반적으로 더 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 위험&lt;/td&gt;
&lt;td&gt;Stack Overflow 등&lt;/td&gt;
&lt;td&gt;Memory Leak, 잘못된 해제 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 메모리 할당이 필요한 이유는 크게 두 가지로 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;b&gt;런타임에 필요한 메모리 크기가 결정되는 데이터를 처리하기 위해서&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 &lt;b&gt;객체의 생명주기를 현재 함수나 스코프와 분리해 관리하기 위해서&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 Heap 메모리를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Heap은 Stack보다 유연한 대신 동적 할당과 해제에 추가적인 관리 비용이 들고, 수명 관리에 실패하면 메모리 누수와 같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 동적 메모리를 공부할 때는 단순히 &quot;new로 메모리를 만든다&quot;라고 이해하기보다,&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;왜 동적으로 만들어야 하는가?
누가 소유하는가?
언제까지 살아 있어야 하는가?
언제 해제해야 하는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 함께 생각하는 것이 중요하다.&lt;/p&gt;</description>
      <category>C++/C++ 문법과 핵심 개념</category>
      <category>heap</category>
      <category>MemoryLeak</category>
      <category>Stack</category>
      <category>동적메모리할당</category>
      <category>메모리</category>
      <category>메모리누수</category>
      <category>메모리영역</category>
      <category>전역변수</category>
      <category>정적변수</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/56</guid>
      <comments>https://lo88ng.tistory.com/56#entry56comment</comments>
      <pubDate>Thu, 27 Aug 2026 10:49:46 +0900</pubDate>
    </item>
    <item>
      <title>C++ 멤버 접근 제어 방법 - Getter/Setter, friend, using</title>
      <link>https://lo88ng.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 설계할 때 멤버 변수를 private으로 두는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 객체의 내부 상태를 직접 수정하지 못하게 하고, 클래스가 정해둔 방식으로만 상태를 변경하도록 만들기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 실제 코드를 작성하다 보면 외부에서 private 멤버의 값을 읽거나 수정해야 하는 상황도 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 단순히 멤버를 public으로 바꾸기보다는, 필요한 범위만 열어주는 여러 방법을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;Getter/Setter, friend,&lt;/b&gt; 그리고 상속받은 멤버의 접근 수준을 다시 노출할 때 사용하는 &lt;b&gt;using&lt;/b&gt;을 통해 C++의 접근 제어 방법을 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Getter와 Setter&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 방법은 public 멤버 함수를 통해 private 멤버에 접근하도록 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 내부의 멤버 함수는 자신의 private 멤버에 접근할 수 있기 때문에 가능한 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같이 HP를 private으로 두고 Getter와 Setter를 제공할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
public:
    int GetPrivateHP()
    {
        return PrivateHP;
    }

    void SetPrivateHP(int InPrivateHP)
    {
        PrivateHP = InPrivateHP;
    }

private:
    int PrivateHP;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서는 PrivateHP에 직접 접근하지 않고 공개된 함수를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Character Player;

Player.SetPrivateHP(100);

int HP = Player.GetPrivateHP();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 보면 단순히 값을 읽고 쓰는 것뿐이라 public 멤버와 큰 차이가 없어 보일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 함수로 접근 경로를 만들면 나중에 &lt;b&gt;값을 변경하는 규칙을 추가&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 HP가 0보다 작아지지 않도록 제한할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
public:
    int GetHP() const
    {
        return HP;
    }

    void SetHP(int InHP)
    {
        if (InHP &amp;lt; 0)
        {
            HP = 0;
            return;
        }

        HP = InHP;
    }

private:
    int HP = 100;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서는 여전히 같은 방식으로 사용하지만,&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Player.SetHP(-100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 상태 변경 규칙은 Character가 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Getter와 Setter를 사용하는 이유는 단순히 private 멤버에 우회해서 접근하기 위해서라기보다, &lt;b&gt;객체가 자신의 상태 변경 규칙을 관리할 수 있도록 하기 위해서&lt;/b&gt;라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Setter가 항상 필요한 것은 아니다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버를 private으로 만들었다고 해서 무조건 Getter와 Setter를 둘 다 만들 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 HP는 외부에서 확인할 필요는 있지만 임의로 설정하게 하고 싶지는 않을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
public:
    int GetHP() const
    {
        return HP;
    }

    void TakeDamage(int Damage)
    {
        HP -= Damage;
    }

private:
    int HP = 100;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 외부에서는 HP를 읽을 수 있지만 직접 값을 설정할 수는 없다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Character Player;

int HP = Player.GetHP();

Player.TakeDamage(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 다음처럼 모든 private 멤버에 기계적으로 Getter와 Setter를 만들어버리면,&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Player.SetHP(999999);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 public 멤버로 공개한 것과 큰 차이가 없어질 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 필요한 동작 자체를 함수로 제공하는 것이 더 자연스러운 경우도 많다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;TakeDamage()
Heal()
ConsumeMana()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;private 데이터
        &amp;darr;
필요한 public 인터페이스만 제공
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하는 형태가 캡슐화의 의도에 더 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;friend 키워드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 &lt;b&gt;특정 외부 함수나 특정 클래스만&lt;/b&gt; 내 클래스의 private, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;protected&lt;/span&gt; 멤버에 직접 접근하도록 &lt;b&gt;허용&lt;/b&gt;하고 싶을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 friend 키워드를 사용해 권한을 부여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;friend 선언이 friend 클래스/함수에 대한 전방선언 역할도 같이 하기 때문에 일반적으로 별도의 전방선언이 필요 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;*다만 friend 함수는 이름 탐색 규칙이 일반적인 함수 선언과 조금 다를 수 있으므로, 외부에서 명시적으로 사용할 함수라면 별도의 선언을 두는 경우도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;friend 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 특정 함수 하나를 friend로 지정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

class Character
{
private:
    int HP = 100;
    int MP = 50;

    friend void PrintCharacterInfo(const Character&amp;amp; CharacterObj);
};

void PrintCharacterInfo(const Character&amp;amp; CharacterObj)
{
    cout &amp;lt;&amp;lt; &quot;HP : &quot; &amp;lt;&amp;lt; CharacterObj.HP &amp;lt;&amp;lt; '\n';
    cout &amp;lt;&amp;lt; &quot;MP : &quot; &amp;lt;&amp;lt; CharacterObj.MP &amp;lt;&amp;lt; '\n';
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PrintCharacterInfo()는 Character의 멤버 함수가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Character 내부에서 friend로 선언했기 때문에 private 멤버에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 friend 함수가 클래스의 멤버가 되는 것은 아니라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때는 일반 함수와 동일하다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int main()
{
    Character Player;

    PrintCharacterInfo(Player);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;friend 클래스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 하나가 아니라 특정 클래스 전체에 접근 권한을 줄 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
private:
    int HP = 100;
    int MP = 50;

    friend class GameManager;
};

class GameManager
{
public:
    void Heal(Character&amp;amp; CharacterObj)
    {
        CharacterObj.HP += 100; // private 접근 가능
        CharacterObj.MP += 50;  // private 접근 가능
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 GameManager의 멤버 함수에서는 Character의 private 멤버에 직접 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

class Character
{
private:
    int HP = 100;
    int MP = 50;

    friend void PrintCharacterInfo(const Character&amp;amp; CharacterObj);
    friend class GameManager;
};

class GameManager
{
public:
    void Heal(Character&amp;amp; CharacterObj)
    {
        CharacterObj.HP += 100;
        CharacterObj.MP += 50;
    }
};

void PrintCharacterInfo(const Character&amp;amp; CharacterObj)
{
    cout &amp;lt;&amp;lt; &quot;HP : &quot; &amp;lt;&amp;lt; CharacterObj.HP &amp;lt;&amp;lt; '\n';
    cout &amp;lt;&amp;lt; &quot;MP : &quot; &amp;lt;&amp;lt; CharacterObj.MP &amp;lt;&amp;lt; '\n';
}

int main()
{
    Character Player;

    GameManager Manager;

    PrintCharacterInfo(Player);

    Manager.Heal(Player);

    cout &amp;lt;&amp;lt; &quot;===== Heal =====\n&quot;;

    PrintCharacterInfo(Player);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;friend는 단방향 관계이다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;friend 관계는 자동으로 서로에게 적용되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 A가 B를 friend로 선언했다고 하자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class A
{
private:
    int ValueA;

    friend class B;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 B는 A의 private 멤버에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 A가 자동으로 B의 private 멤버에 접근할 수 있는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;A가 B를 friend로 선언

B &amp;rarr; A private 접근 가능
A &amp;rarr; B private 접근 가능하다는 뜻은 아님
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요하다면 B도 별도로 A를 friend로 선언해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;friend는 캡슐화를 완전히 무시하는 기능일까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;friend는 분명 외부 코드에 private 접근 권한을 제공하기 때문에 남용하면 캡슐화를 약하게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 반드시 피해야 하는 기능은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 모든 코드에 데이터를 공개하는 것이 아니라 &lt;b&gt;특정 함수나 특정 타입에만 제한적으로 권한을 제공한다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public
&amp;rarr; 모든 외부 코드에서 접근 가능

friend
&amp;rarr; 내가 지정한 특정 대상만 접근 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 서로 강하게 협력하는 타입이나 연산자 오버로딩처럼 클래스 내부 데이터 접근이 자연스러운 경우 등에 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 접근 권한이 필요한 대상이 많아지기 시작한다면 클래스의 책임이나 인터페이스 설계를 다시 확인할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;using으로 상속받은 멤버를 다시 노출하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;using도 접근 제어와 관련해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;u&gt;다만 using은 앞에서 본 Getter/Setter나 friend와는 역할이 조금 다르다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;using은 private 멤버에 접근할 수 있게 만들어주는 기능이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상속을 통해 &lt;u&gt;이미 접근 가능한 멤버&lt;/u&gt;의 이름을 현재 클래스의 다른 접근 영역에 다시 노출하는 데 사용할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 부모 클래스에 protected 멤버가 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
protected:
    int HP;
    
    void Move()
    {
        std::cout &amp;lt;&amp;lt; &quot;Move\n&quot;;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monster는 Character를 public 상속하므로 &lt;b&gt;HP에 접근할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 외부에서는 여전히 HP가 protected이기 때문에 접근할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Monster : public Character
{
};

int main()
{
    Monster M;

    M.HP = 100; // Error!
    M.Move();   // Error!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 using을 public 영역에 선언할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Monster : public Character
{
public:
    using Character::HP;
    using Character::Move; // 메서드도 다시 노출 가능
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Monster에서는 HP가 외부에 공개된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int main()
{
    Monster M;

    M.HP = 100; // 가능
    M.Move();   // 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, public 상속 받은 자식 클래스 객체에서도 public 접근성이 그대로 이어진다.&lt;/p&gt;
&lt;pre id=&quot;code_1787749413165&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BabyMonster : public Monster // public 상속
{
};

int main()
{
    BabyMonster M;
    M.Move(); // 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 개념적으로 다음과 같은 변화이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;Character::HP
protected

        &amp;darr; public 영역에서 using

Monster::HP
public
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;using으로 private 멤버도 공개할 수 있을까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;불가능하다.&lt;/span&gt;&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 부모의 멤버가 private이라면,&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
private:
    int HP;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스 자체가 HP에 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 다시 노출할 수도 없다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Monster : public Character
{
public:
    using Character::HP; // Error!
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;using이 부모의 접근 제한을 무시하는 기능은 아니기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, using을 이렇게 이해하는 편이 좋다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;private을 뚫는 기능 X

이미 접근 가능한 상속 멤버를
현재 클래스의 접근 영역에 다시 노출하는 기능 O
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Getter/Setter, friend, using은 서로 역할이 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지를 모두 접근 제어와 관련된 기능으로 볼 수 있지만 목적은 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;방법&lt;/td&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Getter / Setter&lt;/td&gt;
&lt;td&gt;private 상태에 접근할 공개 인터페이스 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;friend&lt;/td&gt;
&lt;td&gt;특정 함수나 클래스에 private/protected 접근 권한 부여&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;using&lt;/td&gt;
&lt;td&gt;상속받아 이미 접근 가능한 멤버를 다른 접근 수준으로 다시 노출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 using은 private 멤버에 접근하기 위한 방법으로 보면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Getter/Setter와 friend는 실제로 private 데이터와 관련된 접근 방법이지만, using은 &lt;b&gt;상속 관계에서 접근 수준을 다시 노출하는 기능&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 멤버를 private으로 만드는 이유는 단순히 외부에서 사용하지 못하게 막기 위해서가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체가 자신의 상태를 직접 관리하고, 외부에서는 필요한 인터페이스를 통해서만 상태를 다루도록&lt;/b&gt; 만들기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;public 멤버 함수&lt;/b&gt;&lt;/span&gt;를 통해 필요한 기능을 인터페이스로 제공하는 것이다.&lt;br /&gt;이를 통해 객체는 자신의 &lt;b&gt;상태 변경 규칙&lt;/b&gt;을 유지할 수 있다.&lt;/li&gt;
&lt;li&gt;특정 함수나 클래스가 내부 데이터에 직접 접근해야 한다면,&amp;nbsp; &lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;단방향&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;접근 권한&lt;/b&gt;을 주는&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;friend&lt;/b&gt;&lt;/span&gt;를 사용할 수도 있다.&lt;br /&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;다만 필요 이상으로 사용하면 클래스 간 결합도가 높아질 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;마지막으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;using&lt;/b&gt;&lt;/span&gt;은 조금 성격이 다르다. 부모로부터 상속받아 이미 접근 가능한 멤버를 현재 클래스의 &lt;b&gt;원하는 접근 영역에 다시 노출&lt;/b&gt;할 때 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>C++/C++ 문법과 핵심 개념</category>
      <category>c++</category>
      <category>friend</category>
      <category>getter</category>
      <category>private</category>
      <category>setter</category>
      <category>Using</category>
      <category>객체지향</category>
      <category>상속</category>
      <category>접근지정자</category>
      <category>캡슐화</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/55</guid>
      <comments>https://lo88ng.tistory.com/55#entry55comment</comments>
      <pubDate>Wed, 26 Aug 2026 22:13:50 +0900</pubDate>
    </item>
    <item>
      <title>C++ Struct와 Class의 차이, 서로 상속할 수 있을까?</title>
      <link>https://lo88ng.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 새로운 타입을 정의하다 보면 struct와 class 중 무엇을 사용해야 할지 고민하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 struct는 단순히 데이터를 묶는 용도이고, class는 함수와 객체 지향 기능을 사용하는 타입이라고 생각하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 C++의 struct와 class는 생각보다 훨씬 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자, 소멸자, 상속, 가상 함수, 다형성 등 거의 모든 기능을 동일하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 굳이 두 문법을 나누어 사용하는 이유는 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 struct와 class의 실제 차이가 무엇인지, 그리고 코드에서 어떤 기준으로 구분해서 사용하는지 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Struct와 Class는 기능적으로 거의 같다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C에서는 struct가 주로 여러 데이터를 하나로 묶는 용도로 사용되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 C++의 struct는 클래스가 사용할 수 있는 객체 지향 기능을 대부분 동일하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 struct에도 생성자와 멤버 함수를 정의할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct Character
{
    Character()
    {
    }

    void Move()
    {
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속과 가상 함수도 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct Character
{
    virtual void Move()
    {
    }
};

struct Monster : public Character
{
    void Move() override
    {
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, C++에서 다음과 같은 기능은 struct와 class 모두 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;생성자
소멸자
멤버 함수
상속
가상 함수
다형성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 둘의 차이는 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 차원에서 중요한 차이는 다음 두 가지이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 기본 접근 지정자
2. 기본 상속 지정자
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;struct는 기본값이 public이고, class는 기본값이 private이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접근 지정자란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 지정자는 멤버 변수나 멤버 함수에 &lt;b&gt;어디에서 접근할 수 있는지&lt;/b&gt;를 제어하는 키워드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 다음 세 가지 접근 지정자를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public
private
protected
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;public&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public으로 선언된 멤버는 외부에서도 접근할 수 있다. 따라서 보통 객체가 외부에 공개해야 하는 기능을 public으로 둔다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
public:
    int HP;

    void TakeDamage(int Damage)
    {
        HP -= Damage; // 클래스 내부에서 접근 가능
    }
};

class Monster : public Character
{
public:
    void TakeDamage(int Damage)
    {
        HP -= Damage; // 자식 클래스에서도 접근 가능
    }
};

int main()
{
    Monster Monster1;
    Monster1.HP = 100; // 외부에서도 접근 가능
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;private&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private 멤버는 해당 클래스 내부에서만 직접 접근할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;외부뿐만 아니라 자식 클래스에서도 부모의 private 멤버에는 직접 접근할 수 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 외부에서 객체의 내부 상태를 마음대로 변경하지 못하도록 제한할 수 있기 때문에 private은 &lt;b&gt;캡슐화&lt;/b&gt;를 구현하는 중요한 수단이 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
private:
    int HP;

public:
    void TakeDamage(int Damage)
    {
        HP -= Damage; // 클래스 내부에서는 접근 가능
    }
};

class Monster : public Character
{
public:
    void TakeDamage(int Damage)
    {
        HP -= Damage; // Error! 부모의 private 멤버
    }
};

int main()
{
    Monster Monster1;
    Monster1.HP = 100; // Error!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;protected&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protected는 외부에서는 접근할 수 없지만 자식 클래스에서는 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
protected:
    int HP;

public:
    void TakeDamage(int Damage)
    {
        HP -= Damage;
    }
};

class Monster : public Character
{
public:
    void TakeDamage(int Damage)
    {
        HP -= Damage; // 자식 클래스에서는 접근 가능
    }
};

int main()
{
    Monster Monster1;
    Monster1.HP = 100; // Error! 외부에서는 접근 불가
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;접근 지정자&lt;/td&gt;
&lt;td&gt;클래스 내부&lt;/td&gt;
&lt;td&gt;자식 클래스&lt;/td&gt;
&lt;td&gt;외부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;protected&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protected를 사용하면 자식 클래스에서 부모의 데이터를 직접 다룰 수 있어 편리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 자식 클래스가 부모 클래스의 내부 구현에 강하게 의존하게 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 무조건 protected로 열어두기보다는 가능한 한 데이터를 private으로 숨기고, 필요한 기능만 함수로 제공하는 편이 유지보수에는 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Struct와 Class의 첫 번째 차이 - 기본 접근 지정자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct와 class의 첫 번째 차이는 &lt;b&gt;접근 지정자를 생략했을 때 적용되는 기본값&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;struct의 기본 접근 지정자는 public이다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct SMyStruct
{
    float X; // 기본적으로 public
    float Y;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 외부에서 바로 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;SMyStruct V01;

V01.X = 10.0f;
V01.Y = 20.0f;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반면 class의 기본 접근 지정자는 private이다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class CMyClass
{
    float X; // 기본적으로 private
    float Y;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 외부에서 접근할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;CMyClass V02;

V02.X = 10.0f; // Error! Member is inaccessible
V02.Y = 20.0f; // Error! Member is inaccessible
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다음 두 코드는 같은 의미이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct SMyStruct
{
    float X;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct SMyStruct
{
public:
    float X;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class는 반대이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class CMyClass
{
    float X;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class CMyClass
{
private:
    float X;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;타입&lt;/td&gt;
&lt;td&gt;기본 접근 지정자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;struct&lt;/td&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;class&lt;/td&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Struct와 Class의 두 번째 차이 - 기본 상속 지정자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct와 class에는 한 가지 차이가 더 있다. 바로 &lt;b&gt;기본 상속 지정자&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모의 멤버를 자식 입장에서 어떤 접근 수준으로 상속받을 것인지 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속 지정자는 다음 코드에서 public이 들어가는 부분을 말한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Monster : public Character
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Struct의 기본 상속은 public이다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 두 코드는 같은 의미이다.&lt;/p&gt;
&lt;pre class=&quot;capnproto&quot;&gt;&lt;code&gt;struct CharacterStruct
{
};

struct MonsterStruct : CharacterStruct
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;capnproto&quot;&gt;&lt;code&gt;struct CharacterStruct
{
};

struct MonsterStruct : public CharacterStruct
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Class의 기본 상속은 private이다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 두 코드는 같은 의미이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
};

class Monster : Character
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Character
{
};

class Monster : private Character
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도하지 않은 private 상속이 발생하면 단순히 멤버 접근 문제에서 끝나는 것이 아니라 업캐스팅이나 다형성에도 영향을 줄 수 있기 때문에 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;public 상속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 객체 지향 상속에서 주로 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 멤버의 접근 수준은 다음과 같이 이어진다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;부모&lt;/td&gt;
&lt;td&gt;자식에서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;protected&lt;/td&gt;
&lt;td&gt;protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;td&gt;직접 접근 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
public:
    int PublicHP;

protected:
    int ProtectedHP;

private:
    int PrivateHP;
};

class Monster : public Character
{
    void Test()
    {
        PublicHP = 100;       // 가능
        ProtectedHP = 100;    // 가능
        PrivateHP = 100;      // Error!
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향에서 말하는 &lt;b&gt;is-a 관계&lt;/b&gt;의 상속은 일반적으로 public 상속으로 표현한다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Monster is a Character
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Monster 객체를 Character 타입으로 취급할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;Monster M;
Character* Ptr = &amp;amp;M;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;private 상속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private 상속에서는 부모의 public, protected 멤버가 &lt;u&gt;&lt;b&gt;자식 입장에서 private으로 취급&lt;/b&gt;&lt;/u&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모의 실제 멤버가 private으로 변경되는 것은 아니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff;&quot;&gt;부모&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff;&quot;&gt;private 상속 후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;public&lt;/td&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;protected&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;private&lt;/td&gt;
&lt;td&gt;직접 접근 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
public:
    int PublicHP;

protected:
    int ProtectedHP;

private:
    int PrivateHP;
};

class Monster : private Character
{
    void Test()
    {
        PublicHP = 100;    // Monster 입장에서 private 멤버로 상속됨
        ProtectedHP = 100; // Monster 입장에서 private 멤버로 상속됨
        //PrivateHP = 100;   // 부모의 private 멤버 - 직접 접근 불가
    }
};

class BabyMonster : public Monster
{
    void Test2()
    {
        //PublicHP = 100;    // Monster에서 private으로 상속됨 - 접근 불가
        //ProtectedHP = 100; // Monster에서 private으로 상속됨 - 접근 불가
        //PrivateHP = 100;   // Character의 private 멤버 - 직접 접근 불가
    }
};

int main() {
    
    Monster M;
    //M.PublicHP = 100;   // Monster에서 private으로 상속됨 - 접근 불가
    //M.ProtectedHP= 100; // Monster에서 private으로 상속됨 - 접근 불가
    //M.PrivateHP= 100;   // Character의 private 멤버 - 직접 접근 불가
    
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monster를 기준으로 접근 수준이 private처럼 취급되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Monster 내부에서는 사용할 수 있지만, 외부 &amp;amp; 그 아래의 자식 클래스에서는 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;protected 상속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protected 상속에서는 부모의 public, protected 멤버가 자식에서 protected로 취급된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 현재 클래스와 그 아래의 자식 클래스에서는 접근할 수 있지만 외부에서는 접근할 수 없다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;부모&lt;/td&gt;
&lt;td&gt;protected 상속 후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;td&gt;protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;protected&lt;/td&gt;
&lt;td&gt;protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;td&gt;직접 접근 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
public:
    int PublicHP;

protected:
    int ProtectedHP;

private:
    int PrivateHP;
};

class Monster : protected Character
{
    void Test()
    {
        PublicHP = 100;    // Monster 입장에서 protected 멤버로 상속됨
        ProtectedHP = 100; // Monster 입장에서 protected 멤버로 상속됨
        //PrivateHP = 100;   // 부모의 private 멤버 - 직접 접근 불가
    }
};

class BabyMonster : public Monster
{
    void Test2()
    {
        PublicHP = 100;    // Monster에서 protected으로 상속되어 접근 가능
        ProtectedHP = 100; // Monster에서 protected으로 상속되어 접근 가능
        //PrivateHP = 100;   // Character의 private 멤버 - 직접 접근 불가
    }
};

int main() {
    
    Monster M;
    //M.PublicHP = 100;   // Monster에서 protected으로 상속됨 - 접근 불가
    //M.ProtectedHP= 100; // Monster에서 protected으로 상속됨 - 접근 불가
    //M.PrivateHP= 100;   // Character의 private 멤버 - 직접 접근 불가
    
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Struct와 Class는 서로 상속할 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct와 class는 상속, 가상 함수, 다형성 등의 기능을 동일하게 지원하기 때문에 서로 자유롭게 상속할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;struct Character
{
};

class Monster : public Character
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;class Character
{
};

struct Monster : public Character
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, &lt;b&gt;상속 지정자를 생략했을 때 적용되는 기본값이 다르다는 점&lt;/b&gt;을 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 코드를 보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;struct Character
{
public:
    int HP;

    void Move()
    {
        cout &amp;lt;&amp;lt; &quot;Character Move\n&quot;;
    }
};

class Monster : Character // private 상속
{
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 상속 지정자는 &lt;b&gt;부모가 아니라 자식 타입의 선언 방식&lt;/b&gt;에 따라 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Monster가 class이므로 다음 코드는 private 상속이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 외부에서는 다음과 같이 사용할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Monster M;

M.Move(); // Error! Member is inaccessible
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 상속 지정자를 놓치면 업캐스팅과 다운캐스팅이 막힌다&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct Character
{
public:
    void Move()
    {
        cout &amp;lt;&amp;lt; &quot;Character Move\n&quot;;
    }
};

class Monster : Character // private 상속
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;private 상속은 멤버 접근뿐 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;외부에서 부모-자식 타입 관계를 이용하는 것에도 영향을 준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;외부에서는 Monster*를 Character*로 업캐스팅할 수 없다&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int main()
{
    Monster M;

    Character* Ptr = &amp;amp;M; // 컴파일 에러

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러는 Monster가 Character를 상속하고 있다는 사실 자체는 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 두 타입 사이의 변환 관계가 없는 것은 아니다. &lt;b&gt;문제는 그 상속 관계가 private이라는 점이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main()과 같은 외부 코드는 Monster의 private 상속 관계에 접근할 권한이 없기 때문에 다음 변환을 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 컴파일 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Monster 내부에서는 업캐스팅할 수 있다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Monster의 멤버 함수 내부에서는 자신의 private 상속 관계에 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Monster : Character
{
public:
    Character* GetCharacter()
    {
        return this;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Monster* &amp;rarr; Character* 암시적 업캐스팅은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 정적 타입과 상속 관계를 알고 있고, 해당 상속 관계에 접근할 권한도 있기 때문에 컴파일 타임에 자동으로 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드를 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

struct Character
{
public:
    void Move()
    {
        cout &amp;lt;&amp;lt; &quot;Character Move\n&quot;;
    }
};

class Monster : Character // private 상속
{
public:
    Character* GetCharacter()
    {
        return this; // 가능
    }

    void TestInside()
    {
        Character* Ptr = this; // 클래스 내부에서는 업캐스팅 가능
        Ptr-&amp;gt;Move();
    }
};

int main()
{
    Monster M;

    // 1. Monster 내부에서는 Character*로 변환 가능
    M.TestInside();

    // 2. public 함수가 Character*를 반환하는 것도 가능
    Character* Ptr1 = M.GetCharacter();
    Ptr1-&amp;gt;Move();
    
    // 3. 외부에서 직접 업캐스팅은 불가능
    //Character* Ptr2 = &amp;amp;M; // 컴파일 에러

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ptr1은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Monster 객체 내부에 존재하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Character 베이스 서브오브젝트&lt;/b&gt;를 가리킨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, Character의 인터페이스를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;Character* Ptr1 = M.GetCharacter();

Ptr1-&amp;gt;Move(); // 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Monster 객체
└── Character 베이스 서브오브젝트
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;외부에서는 다시 Monster*로 다운캐스팅하는 것도 제한된다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Character*를 얻었다고 해서 외부(ex. main 함수)에서 자유롭게 다시 Monster*로 변환할 수 있는 것도 아니다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Character* Ptr1 = M.GetCharacter();

// Monster* M2 = static_cast&amp;lt;Monster*&amp;gt;(Ptr1); // Error!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 변환 역시 private 상속 관계를 사용해야 하므로 외부에서는 허용되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 외부에서는 다음 두 방향 모두 막힌다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Monster*   &amp;rarr; Character*   // 업캐스팅 불가
Character* &amp;rarr; Monster*     // 다운캐스팅 불가&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 &lt;b&gt;&lt;/b&gt;단지 현재 코드가 그 private 상속 관계를 사용할 권한이 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Monster 내부에서는 다운캐스팅도 가능하다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 Monster 내부에서는 private 상속 관계에 접근할 수 있으므로 다운캐스팅도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;class Monster : Character
{
public:
    void TestInside()
    {
    	// Monster* &amp;rarr; Character*
        Character* CharacterPtr = this;
        
        // Character* &amp;rarr; Monster*
        Monster* MonsterPtr =
            static_cast&amp;lt;Monster*&amp;gt;(CharacterPtr);

        MonsterPtr-&amp;gt;Move();
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 다음 두 변환이 모두 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 Monster의 멤버 함수 내부에서 수행되기 때문에 private 상속 관계에 접근할 권한이 있다.\&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 private 상속을 다음과 같이 이해하면 된다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;private 상속
&amp;ne; 상속 관계가 사라짐

private 상속
= 외부에서 그 상속 관계를 타입 변환에 사용할 수 없음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다형성에도 영향을 준다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private 상속 때문에 가상 함수 자체가 동작하지 않는 것은 아니다. 문제는 외부에서 자식 객체를 부모 타입으로 업캐스팅할 수 없다는 것이다. 이 때문에 부모 포인터나 참조를 이용해 여러 자식 객체를 동일한 인터페이스로 관리하는 일반적인 런타임 다형성 구조에 참여할 수 없게 된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

struct Character
{
    virtual ~Character() = default;

    virtual void Move()
    {
        cout &amp;lt;&amp;lt; &quot;Character Move\n&quot;;
    }
};

class Player : public Character
{
public:
    void Move() override
    {
        cout &amp;lt;&amp;lt; &quot;Player Move\n&quot;;
    }
};

class Monster : Character // 실수: public 생략 &amp;rarr; private 상속
{
public:
    void Move() override
    {
        cout &amp;lt;&amp;lt; &quot;Monster Move\n&quot;;
    }
};

int main()
{
    Player P;
    Monster M;

    vector&amp;lt;Character*&amp;gt; Characters;

    Characters.push_back(&amp;amp;P); // 가능
    Characters.push_back(&amp;amp;M); // Error!

    for (Character* CharacterPtr : Characters)
    {
        CharacterPtr-&amp;gt;Move();
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 객체 지향적인 &lt;b&gt;is-a 관계를 표현&lt;/b&gt;할 목적이라면 다음처럼 &lt;b&gt;public을 명시&lt;/b&gt;하는 편이 좋다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class Monster : public Character
{
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 struct와 class를 서로 다른 종류의 타입이라고 생각하기 쉬웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 C++에서는 생성자, 소멸자, 상속, 가상 함수, 다형성 등 거의 동일한 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실질적인 언어 차이는 다음 두 가지이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;struct&lt;/td&gt;
&lt;td&gt;class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 접근 지정자&lt;/td&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 상속 지정자&lt;/td&gt;
&lt;td&gt;public&lt;/td&gt;
&lt;td&gt;private&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 실제 코드에서는 역할을 구분하기 위해 서로 다르게 사용하는 경우가 많다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Struct
&amp;rarr; 데이터를 표현하는 값 타입

Class
&amp;rarr; 상태를 관리하고 책임을 수행하는 객체&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 이것은 C++ 문법이 강제하는 규칙은 아니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 읽는 사람에게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이 타입을 어떤 의도로 만들었는지 전달하기 위한 관례&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 개인적으로는 기본 접근 지정자의 차이보다 &lt;b&gt;기본 상속 지정자의 차이를 더 주의해야 한다고 느꼈다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class에서 상속 지정자를 생략하면 의도하지 않은 private 상속이 발생할 수 있고, 이 경우 외부에서 부모 타입으로 업캐스팅할 수 없어 다형성 활용까지 막힐 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 is-a 관계의 상속을 표현할 때는 기본값에 의존하기보다 public을 명시하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C++/C++ 문법과 핵심 개념</category>
      <category>CLASS</category>
      <category>private</category>
      <category>Protected</category>
      <category>Public</category>
      <category>struct</category>
      <category>구조체</category>
      <category>상속</category>
      <category>상속지정자</category>
      <category>접근지정자</category>
      <category>클래스</category>
      <author>loooong</author>
      <guid isPermaLink="true">https://lo88ng.tistory.com/54</guid>
      <comments>https://lo88ng.tistory.com/54#entry54comment</comments>
      <pubDate>Wed, 26 Aug 2026 21:10:48 +0900</pubDate>
    </item>
  </channel>
</rss>