<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sehee-jj.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 30 Jan 2026 15:22:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sehee-jj.log</title>
            <url>https://velog.velcdn.com/images/sehee-jj/profile/34fe17d4-e48b-4db3-bd9e-353e1d5d03b7/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sehee-jj.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sehee-jj" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[C++] Set/Map]]></title>
            <link>https://velog.io/@sehee-jj/C-SetMap</link>
            <guid>https://velog.io/@sehee-jj/C-SetMap</guid>
            <pubDate>Fri, 30 Jan 2026 15:22:26 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-setmap">📚 C++ Set/Map</h2>
<h3 id="🔸-set-집합">🔸 set (집합)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Red-Black Tree (레드-블랙 트리)</strong>로 구현</li>
<li><strong>중복 불가</strong> - 각 요소는 유일해야 함</li>
<li><strong>자동 정렬</strong> - 삽입 시 자동으로 정렬된 상태 유지</li>
<li><strong>Balanced BST</strong> 특성상 모든 연산이 O(log n) 보장</li>
</ul>
<p><strong>시간 복잡도</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td>Insertion (<code>insert</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Deletion (<code>erase</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Search (<code>find</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Access (최소/최대)</td>
<td>O(log n)</td>
</tr>
</tbody></table>
<p><strong>메모리 구조 (Red-Black Tree)</strong></p>
<pre><code>레드-블랙 트리 속성:
1. 모든 노드는 Red 또는 Black
2. 루트는 항상 Black
3. 모든 리프(NULL)는 Black
4. Red 노드의 자식은 모두 Black
5. 모든 경로의 Black 노드 개수는 동일

예시: {10, 20, 30, 40, 50}
         30(B)
        /     \
     20(B)    40(B)
      /         \
   10(R)       50(R)

Stack:
┌──────────────────┐
│ root (pointer)  │ → 루트 노드
│ size            │ → 요소 개수
└──────────────────┘

Heap (각 노드):
┌────────────────────┐
│ color (R/B)       │
│ data              │
│ left (pointer)    │
│ right (pointer)   │
│ parent (pointer)  │
└────────────────────┘</code></pre><p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">#include &lt;set&gt;
#include &lt;iostream&gt;

std::set&lt;int&gt; s;

// ✅ 삽입 - O(log n)
s.insert(30);
s.insert(10);
s.insert(20);
s.insert(10);  // 중복 - 삽입 안 됨!

// ✅ 자동 정렬됨
for (int val : s) {
    std::cout &lt;&lt; val &lt;&lt; &quot; &quot;;  // 10 20 30
}

// ✅ 검색 - O(log n)
auto it = s.find(20);
if (it != s.end()) {
    std::cout &lt;&lt; &quot;Found: &quot; &lt;&lt; *it;
}

// ✅ 삭제 - O(log n)
s.erase(20);

// ✅ 범위 기반 검색
auto lower = s.lower_bound(15);  // &gt;= 15인 첫 요소
auto upper = s.upper_bound(25);  // &gt; 25인 첫 요소</code></pre>
<p><strong>커스텀 비교 함수</strong></p>
<pre><code class="language-cpp">// 내림차순 정렬
std::set&lt;int, std::greater&lt;int&gt;&gt; descSet;

// 커스텀 비교자
struct Person {
    std::string name;
    int age;
};

struct CompareByAge {
    bool operator()(const Person&amp; a, const Person&amp; b) const {
        return a.age &lt; b.age;
    }
};

std::set&lt;Person, CompareByAge&gt; people;</code></pre>
<hr>
<h3 id="🔸-multiset-중복-허용-집합">🔸 multiset (중복 허용 집합)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><code>set</code>과 동일하게 <strong>Red-Black Tree</strong>로 구현</li>
<li><strong>중복 허용</strong> - 동일한 값을 여러 번 삽입 가능</li>
<li><strong>자동 정렬</strong> 유지</li>
<li>시간 복잡도는 <code>set</code>과 동일</li>
</ul>
<p><strong>set vs multiset</strong></p>
<pre><code class="language-cpp">std::set&lt;int&gt; s;
s.insert(10);
s.insert(10);
s.insert(10);
std::cout &lt;&lt; s.size();  // 1 (중복 무시)

std::multiset&lt;int&gt; ms;
ms.insert(10);
ms.insert(10);
ms.insert(10);
std::cout &lt;&lt; ms.size();  // 3 (중복 허용)</code></pre>
<p><strong>중복 요소 처리</strong></p>
<pre><code class="language-cpp">std::multiset&lt;int&gt; ms = {10, 20, 20, 30, 30, 30};

// ✅ 특정 값의 개수 세기 - O(log n + k), k는 중복 개수
int count = ms.count(30);  // 3

// ✅ 특정 값의 범위 찾기
auto range = ms.equal_range(30);
for (auto it = range.first; it != range.second; ++it) {
    std::cout &lt;&lt; *it &lt;&lt; &quot; &quot;;  // 30 30 30
}

// ✅ 특정 값 하나만 삭제
auto it = ms.find(30);
if (it != ms.end()) {
    ms.erase(it);  // 30 하나만 삭제
}

// ✅ 특정 값 모두 삭제
ms.erase(30);  // 30 모두 삭제</code></pre>
<hr>
<h3 id="🔸-map-키-값-쌍">🔸 map (키-값 쌍)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Red-Black Tree</strong>로 구현</li>
<li><strong>키(Key)는 중복 불가</strong>, 값(Value)은 중복 가능</li>
<li><strong>키 기준으로 자동 정렬</strong></li>
<li><code>set</code>과 동일한 시간 복잡도</li>
</ul>
<p><strong>시간 복잡도</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td>Insertion (<code>insert</code>, <code>operator[]</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Deletion (<code>erase</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Search (<code>find</code>, <code>operator[]</code>)</td>
<td>O(log n)</td>
</tr>
</tbody></table>
<p><strong>메모리 구조</strong></p>
<pre><code>Heap (각 노드):
┌────────────────────┐
│ color (R/B)       │
│ pair&lt;Key, Value&gt;  │ ← key와 value를 함께 저장
│ left (pointer)    │
│ right (pointer)   │
│ parent (pointer)  │
└────────────────────┘</code></pre><p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">#include &lt;map&gt;
#include &lt;string&gt;

std::map&lt;std::string, int&gt; ages;

// ✅ 삽입 방법 1: operator[] - O(log n)
ages[&quot;Alice&quot;] = 25;
ages[&quot;Bob&quot;] = 30;

// ✅ 삽입 방법 2: insert - O(log n)
ages.insert({&quot;Charlie&quot;, 35});
ages.insert(std::make_pair(&quot;David&quot;, 40));

// ✅ 검색 - O(log n)
auto it = ages.find(&quot;Alice&quot;);
if (it != ages.end()) {
    std::cout &lt;&lt; it-&gt;first &lt;&lt; &quot;: &quot; &lt;&lt; it-&gt;second;  // Alice: 25
}

// ⚠️ operator[]의 주의점
int age = ages[&quot;Eve&quot;];  // 키가 없으면 자동 생성! (value는 0으로 초기화)
std::cout &lt;&lt; ages.size();  // 5 (Eve가 추가됨)

// ✅ 안전한 검색: at() 사용
try {
    int age = ages.at(&quot;Frank&quot;);  // 키가 없으면 예외 발생
} catch (const std::out_of_range&amp; e) {
    std::cout &lt;&lt; &quot;Key not found!&quot;;
}</code></pre>
<p><strong>iterator 활용</strong></p>
<pre><code class="language-cpp">std::map&lt;std::string, int&gt; scores = {
    {&quot;Alice&quot;, 90},
    {&quot;Bob&quot;, 85},
    {&quot;Charlie&quot;, 95}
};

// ✅ 순회 (키 기준 정렬된 순서)
for (const auto&amp; [name, score] : scores) {
    std::cout &lt;&lt; name &lt;&lt; &quot;: &quot; &lt;&lt; score &lt;&lt; &quot;\n&quot;;
}
// Alice: 90
// Bob: 85
// Charlie: 95</code></pre>
<hr>
<h3 id="🔸-multimap-중복-키-허용">🔸 multimap (중복 키 허용)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><code>map</code>과 동일하게 <strong>Red-Black Tree</strong>로 구현</li>
<li><strong>키 중복 허용</strong></li>
<li><code>operator[]</code> 사용 불가 (어떤 값을 반환할지 모호)</li>
</ul>
<p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">std::multimap&lt;std::string, int&gt; scores;

scores.insert({&quot;Alice&quot;, 90});
scores.insert({&quot;Alice&quot;, 85});  // 같은 키에 다른 값
scores.insert({&quot;Bob&quot;, 95});

// ✅ 특정 키의 모든 값 찾기
auto range = scores.equal_range(&quot;Alice&quot;);
for (auto it = range.first; it != range.second; ++it) {
    std::cout &lt;&lt; it-&gt;second &lt;&lt; &quot; &quot;;  // 85 90 (정렬됨)
}</code></pre>
<hr>
<h3 id="🔸-unordered_set-해시-집합">🔸 unordered_set (해시 집합)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Hash Table</strong>로 구현</li>
<li><strong>중복 불가</strong></li>
<li><strong>정렬되지 않음</strong> (순서 보장 안 됨)</li>
<li>평균 O(1), 최악 O(n) 시간 복잡도</li>
</ul>
<p><strong>시간 복잡도</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>평균</th>
<th>최악</th>
</tr>
</thead>
<tbody><tr>
<td>Insertion</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Deletion</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Search</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
</tbody></table>
<p><strong>메모리 구조 (Hash Table with Separate Chaining)</strong></p>
<pre><code>Hash Table 구조:

해시 함수: hash(value) % bucket_count

Bucket Array (vector):
┌─────┬─────┬─────┬─────┬─────┐
│  0 │  1  │  2  │  3  │  4  │
└──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘
   │     │     │     │     │
   ↓     ↓     ↓     ↓     ↓
  [10]  [21]  NULL [13]  [24]
   ↓     ↓           ↓     ↓
  [30]  NULL        [33]  NULL

각 버킷은 Linked List로 구현 (Separate Chaining)
해시 충돌 발생 시 같은 버킷에 연결

Stack:
┌──────────────────┐
│ buckets (array) │ → 버킷 배열
│ size            │ → 요소 개수
│ bucket_count    │ → 버킷 개수
│ max_load_factor │ → 최대 로드 팩터 (기본 1.0)
└──────────────────┘</code></pre><p><strong>해시 함수와 충돌 처리</strong></p>
<pre><code class="language-cpp">// 해시 값 계산 예시
hash&lt;int&gt; hashFunc;
size_t hashValue = hashFunc(42);

// 버킷 인덱스 = hash(value) % bucket_count
size_t bucketIndex = hashValue % bucket_count;

// ✅ Separate Chaining
// 같은 버킷에 여러 요소가 linked list로 연결됨</code></pre>
<p><strong>Load Factor와 Rehashing</strong></p>
<pre><code class="language-cpp">std::unordered_set&lt;int&gt; us;

// Load Factor = size / bucket_count
// 기본 max_load_factor = 1.0

us.insert(10);
us.insert(20);
us.insert(30);

std::cout &lt;&lt; &quot;Size: &quot; &lt;&lt; us.size() &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; &quot;Bucket count: &quot; &lt;&lt; us.bucket_count() &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; &quot;Load factor: &quot; &lt;&lt; us.load_factor() &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; &quot;Max load factor: &quot; &lt;&lt; us.max_load_factor() &lt;&lt; &quot;\n&quot;;

// ⚠️ load_factor &gt; max_load_factor 되면 Rehashing 발생!
// Rehashing: O(n) - 모든 요소를 새 버킷에 재배치</code></pre>
<p><strong>Rehashing 최적화</strong></p>
<pre><code class="language-cpp">std::unordered_set&lt;int&gt; us;

// ✅ 미리 버킷 예약 - rehashing 방지
us.reserve(1000);  // 최소 1000개 요소를 담을 수 있도록 버킷 확보

// ✅ max_load_factor 조정
us.max_load_factor(0.5);  // 로드 팩터를 낮춰서 충돌 감소 (메모리 trade-off)</code></pre>
<p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">#include &lt;unordered_set&gt;

std::unordered_set&lt;int&gt; us;

// ✅ 삽입 - 평균 O(1)
us.insert(10);
us.insert(20);
us.insert(30);

// ✅ 검색 - 평균 O(1)
if (us.find(20) != us.end()) {
    std::cout &lt;&lt; &quot;Found!&quot;;
}

// ⚠️ 순서 보장 안 됨!
for (int val : us) {
    std::cout &lt;&lt; val &lt;&lt; &quot; &quot;;  // 순서 예측 불가
}</code></pre>
<p><strong>커스텀 타입을 위한 Hash Function 정의</strong></p>
<p>방법 1: <strong>Hash Function Object 생성</strong></p>
<pre><code class="language-cpp">struct Person {
    std::string name;
    int age;

    bool operator==(const Person&amp; other) const {
        return name == other.name &amp;&amp; age == other.age;
    }
};

// 커스텀 해시 함수 객체
struct PersonHash {
    size_t operator()(const Person&amp; p) const {
        // 여러 필드를 조합한 해시 값 계산
        size_t h1 = std::hash&lt;std::string&gt;{}(p.name);
        size_t h2 = std::hash&lt;int&gt;{}(p.age);
        return h1 ^ (h2 &lt;&lt; 1);  // XOR과 shift 조합
    }
};

// 사용
std::unordered_set&lt;Person, PersonHash&gt; people;
people.insert({&quot;Alice&quot;, 25});</code></pre>
<p>방법 2: <strong>std 네임스페이스에 특수화 주입</strong></p>
<pre><code class="language-cpp">struct Person {
    std::string name;
    int age;

    bool operator==(const Person&amp; other) const {
        return name == other.name &amp;&amp; age == other.age;
    }
};

// std::hash 특수화
namespace std {
    template&lt;&gt;
    struct hash&lt;Person&gt; {
        size_t operator()(const Person&amp; p) const {
            size_t h1 = hash&lt;string&gt;{}(p.name);
            size_t h2 = hash&lt;int&gt;{}(p.age);
            return h1 ^ (h2 &lt;&lt; 1);
        }
    };
}

// 사용 (추가 템플릿 인자 불필요)
std::unordered_set&lt;Person&gt; people;
people.insert({&quot;Alice&quot;, 25});</code></pre>
<p><strong>커스텀 Equality Operator</strong></p>
<pre><code class="language-cpp">// 방법 1: 클래스 내부에 operator== 정의 (위 예시 참고)

// 방법 2: 별도의 함수 객체
struct PersonEqual {
    bool operator()(const Person&amp; a, const Person&amp; b) const {
        return a.name == b.name &amp;&amp; a.age == b.age;
    }
};

std::unordered_set&lt;Person, PersonHash, PersonEqual&gt; people;</code></pre>
<hr>
<h3 id="🔸-unordered_map-해시-맵">🔸 unordered_map (해시 맵)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Hash Table</strong>로 구현</li>
<li><strong>키 중복 불가</strong></li>
<li><code>unordered_set</code>과 동일한 시간 복잡도</li>
<li><strong>실무에서 가장 많이 사용됨</strong></li>
<li><strong>알고리즘 면접에서 자주 출제됨</strong></li>
</ul>
<p><strong>시간 복잡도</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>평균</th>
<th>최악</th>
</tr>
</thead>
<tbody><tr>
<td>Insertion (<code>insert</code>, <code>operator[]</code>)</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Deletion (<code>erase</code>)</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Search (<code>find</code>, <code>operator[]</code>)</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
</tbody></table>
<p><strong>메모리 구조</strong></p>
<pre><code>Bucket Array:
┌──────┬──────┬──────┬──────┐
│  0   │  1  │  2   │  3  │
└───┬──┴──┬───┴──┬───┴──┬───┘
   │     │      │      │
   ↓     ↓      ↓      ↓
 {k1:v1} NULL {k2:v2} NULL
   ↓                   ↓
 {k3:v3}             {k4:v4}

각 노드는 pair&lt;Key, Value&gt; 저장</code></pre><p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">#include &lt;unordered_map&gt;
#include &lt;string&gt;

std::unordered_map&lt;std::string, int&gt; phoneBook;

// ✅ 삽입 - 평균 O(1)
phoneBook[&quot;Alice&quot;] = 1234;
phoneBook[&quot;Bob&quot;] = 5678;
phoneBook.insert({&quot;Charlie&quot;, 9012});

// ✅ 검색 - 평균 O(1)
auto it = phoneBook.find(&quot;Alice&quot;);
if (it != phoneBook.end()) {
    std::cout &lt;&lt; it-&gt;first &lt;&lt; &quot;: &quot; &lt;&lt; it-&gt;second;
}

// ✅ operator[] - 평균 O(1)
int number = phoneBook[&quot;Alice&quot;];  // 1234

// ⚠️ 키가 없으면 자동 생성
int unknown = phoneBook[&quot;David&quot;];  // David 키 생성, value = 0

// ✅ 성능 최적화
phoneBook.reserve(10000);  // 예상 크기 미리 예약</code></pre>
<p><strong>실무 활용 예시</strong></p>
<pre><code class="language-cpp">// 빈도수 카운팅
std::unordered_map&lt;char, int&gt; freq;
std::string text = &quot;hello world&quot;;
for (char c : text) {
    freq[c]++;
}

// 캐싱 (Memoization)
std::unordered_map&lt;int, int&gt; cache;
int fibonacci(int n) {
    if (n &lt;= 1) return n;
    if (cache.find(n) != cache.end()) {
        return cache[n];
    }
    cache[n] = fibonacci(n-1) + fibonacci(n-2);
    return cache[n];
}

// 그래프 인접 리스트
std::unordered_map&lt;int, std::vector&lt;int&gt;&gt; graph;
graph[1] = {2, 3};
graph[2] = {4};</code></pre>
<p><strong>커스텀 타입 예시</strong></p>
<pre><code class="language-cpp">struct Point {
    int x, y;
    bool operator==(const Point&amp; other) const {
        return x == other.x &amp;&amp; y == other.y;
    }
};

struct PointHash {
    size_t operator()(const Point&amp; p) const {
        return std::hash&lt;int&gt;{}(p.x) ^ (std::hash&lt;int&gt;{}(p.y) &lt;&lt; 1);
    }
};

std::unordered_map&lt;Point, std::string, PointHash&gt; locations;
locations[{0, 0}] = &quot;Origin&quot;;
locations[{10, 20}] = &quot;Point A&quot;;</code></pre>
<hr>
<h3 id="🔸-unordered_multiset--unordered_multimap">🔸 unordered_multiset / unordered_multimap</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li>각각 <code>unordered_set</code>, <code>unordered_map</code>의 중복 허용 버전</li>
<li>시간 복잡도 동일</li>
<li><code>unordered_multimap</code>은 <code>operator[]</code> 사용 불가</li>
</ul>
<p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">std::unordered_multiset&lt;int&gt; ums = {10, 20, 20, 30, 30, 30};

// 특정 값의 개수
int count = ums.count(30);  // 3

// 특정 값 모두 찾기
auto range = ums.equal_range(30);
for (auto it = range.first; it != range.second; ++it) {
    std::cout &lt;&lt; *it &lt;&lt; &quot; &quot;;
}</code></pre>
<hr>
<h3 id="🔸-set-vs-unordered_set-비교">🔸 set vs unordered_set 비교</h3>
<p><strong>성능 비교</strong></p>
<table>
<thead>
<tr>
<th>특성</th>
<th>set</th>
<th>unordered_set</th>
</tr>
</thead>
<tbody><tr>
<td><strong>구현</strong></td>
<td>Red-Black Tree</td>
<td>Hash Table</td>
</tr>
<tr>
<td><strong>정렬</strong></td>
<td>✅ 자동 정렬</td>
<td>❌ 정렬 안 됨</td>
</tr>
<tr>
<td><strong>평균 검색</strong></td>
<td>O(log n)</td>
<td>O(1) ✅</td>
</tr>
<tr>
<td><strong>최악 검색</strong></td>
<td>O(log n) ✅</td>
<td>O(n)</td>
</tr>
<tr>
<td><strong>메모리</strong></td>
<td>적음</td>
<td>많음 (버킷 배열)</td>
</tr>
<tr>
<td><strong>순서 보장</strong></td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td><strong>iterator 안정성</strong></td>
<td>높음</td>
<td>낮음 (rehashing 시 무효화)</td>
</tr>
</tbody></table>
<p><strong>선택 가이드</strong></p>
<pre><code class="language-cpp">// ✅ set을 사용해야 하는 경우:
// 1. 정렬된 순서가 필요할 때
std::set&lt;int&gt; sorted = {30, 10, 20};
for (int val : sorted) {
    std::cout &lt;&lt; val &lt;&lt; &quot; &quot;;  // 10 20 30 (정렬됨)
}

// 2. 범위 검색이 필요할 때
auto lower = sorted.lower_bound(15);
auto upper = sorted.upper_bound(25);

// 3. 최악의 경우에도 일정한 성능이 필요할 때 (O(log n) 보장)

// ✅ unordered_set을 사용해야 하는 경우:
// 1. 빠른 검색이 최우선일 때 (평균 O(1))
std::unordered_set&lt;int&gt; fast;

// 2. 순서가 중요하지 않을 때

// 3. 데이터 크기를 미리 알고 reserve 가능할 때
fast.reserve(10000);</code></pre>
<p><strong>map vs unordered_map 비교</strong></p>
<table>
<thead>
<tr>
<th>특성</th>
<th>map</th>
<th>unordered_map</th>
</tr>
</thead>
<tbody><tr>
<td><strong>구현</strong></td>
<td>Red-Black Tree</td>
<td>Hash Table</td>
</tr>
<tr>
<td><strong>키 정렬</strong></td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td><strong>평균 검색</strong></td>
<td>O(log n)</td>
<td>O(1) ✅</td>
</tr>
<tr>
<td><strong>최악 검색</strong></td>
<td>O(log n) ✅</td>
<td>O(n)</td>
</tr>
<tr>
<td><strong>실무 사용 빈도</strong></td>
<td>중간</td>
<td>✅ 매우 높음</td>
</tr>
</tbody></table>
<hr>
<h3 id="🔸-hash-function-설계-원칙">🔸 Hash Function 설계 원칙</h3>
<p><strong>좋은 해시 함수의 조건</strong></p>
<ol>
<li><strong>결정론적 (Deterministic)</strong>: 같은 입력은 항상 같은 해시 값</li>
<li><strong>균등 분포 (Uniform Distribution)</strong>: 해시 값이 고르게 분포</li>
<li><strong>효율성 (Efficient)</strong>: 계산이 빠름 (O(1))</li>
<li><strong>충돌 최소화</strong>: 서로 다른 입력에 대해 다른 해시 값</li>
</ol>
<p><strong>해시 조합 기법</strong></p>
<pre><code class="language-cpp">// ❌ 나쁜 예: 단순 더하기 (충돌 많음)
size_t badHash = hash&lt;int&gt;{}(x) + hash&lt;int&gt;{}(y);

// ✅ 좋은 예 1: XOR + Shift
size_t goodHash1 = hash&lt;int&gt;{}(x) ^ (hash&lt;int&gt;{}(y) &lt;&lt; 1);

// ✅ 좋은 예 2: Boost 스타일
size_t goodHash2 = hash&lt;int&gt;{}(x);
goodHash2 ^= hash&lt;int&gt;{}(y) + 0x9e3779b9 + (goodHash2 &lt;&lt; 6) + (goodHash2 &gt;&gt; 2);

// ✅ 좋은 예 3: 여러 필드 조합
struct Data {
    int a, b, c;
};

size_t hash_value(const Data&amp; d) {
    size_t seed = 0;
    seed ^= hash&lt;int&gt;{}(d.a) + 0x9e3779b9 + (seed &lt;&lt; 6) + (seed &gt;&gt; 2);
    seed ^= hash&lt;int&gt;{}(d.b) + 0x9e3779b9 + (seed &lt;&lt; 6) + (seed &gt;&gt; 2);
    seed ^= hash&lt;int&gt;{}(d.c) + 0x9e3779b9 + (seed &lt;&lt; 6) + (seed &gt;&gt; 2);
    return seed;
}</code></pre>
<hr>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>컨테이너 선택 가이드</strong></p>
<pre><code>순서가 필요한가?
├─ YES → set / map
│         - 정렬된 순서 보장
│         - 범위 검색 가능
│         - O(log n) 성능 보장
│
└─ NO → unordered_set / unordered_map
          - 평균 O(1) 성능
          - 메모리 사용량 높음
          - rehashing 주의

중복이 필요한가?
├─ YES → multiset / multimap / unordered_multiset / unordered_multimap
└─ NO → set / map / unordered_set / unordered_map

키-값 쌍이 필요한가?
├─ YES → map / unordered_map
└─ NO → set / unordered_set</code></pre><p><strong>성능 최적화 팁</strong></p>
<ol>
<li><p><strong>unordered_* 사용 시</strong>:</p>
<ul>
<li>예상 크기를 아는 경우 <code>reserve()</code> 사용</li>
<li><code>max_load_factor</code> 조정으로 충돌 제어</li>
<li>좋은 해시 함수 설계</li>
</ul>
</li>
<li><p><strong>set/map 사용 시</strong>:</p>
<ul>
<li>커스텀 비교 함수로 정렬 순서 제어</li>
<li><code>lower_bound</code>, <code>upper_bound</code> 활용</li>
</ul>
</li>
<li><p><strong>일반적인 권장사항</strong>:</p>
<ul>
<li>대부분의 경우: <code>unordered_map</code> (실무 표준)</li>
<li>정렬이 필요한 경우: <code>map</code></li>
<li>중복이 필요한 경우: <code>multiset</code> / <code>multimap</code></li>
</ul>
</li>
</ol>
<p><strong>실무에서 자주 쓰이는 패턴</strong></p>
<pre><code class="language-cpp">// 1. 빈도수 카운팅
unordered_map&lt;string, int&gt; wordCount;

// 2. 중복 제거
unordered_set&lt;int&gt; uniqueValues;

// 3. 캐싱/메모이제이션
unordered_map&lt;int, Result&gt; cache;

// 4. 그래프 표현
unordered_map&lt;Node, vector&lt;Node&gt;&gt; adjacencyList;

// 5. 빠른 조회 테이블
unordered_map&lt;string, UserData&gt; userDatabase;</code></pre>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/container/set">std::set</a>
<a href="https://en.cppreference.com/w/cpp/container/map">std::map</a>
<a href="https://en.cppreference.com/w/cpp/container/unordered_set">std::unordered_set</a>
<a href="https://en.cppreference.com/w/cpp/container/unordered_map">std::unordered_map</a>
<a href="https://en.wikipedia.org/wiki/Red%E2%80%93black_tree">Red-Black Tree</a>
<a href="https://en.wikipedia.org/wiki/Hash_table">Hash Table</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 컨테이너 (Containers)]]></title>
            <link>https://velog.io/@sehee-jj/C-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-Containers</link>
            <guid>https://velog.io/@sehee-jj/C-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-Containers</guid>
            <pubDate>Wed, 21 Jan 2026 05:39:50 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-컨테이너-containers">📚 C++ 컨테이너 (Containers)</h2>
<h3 id="🔸-list-이중-연결-리스트">🔸 list (이중 연결 리스트)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Doubly-Linked List</strong>로 구현</li>
<li>각 노드는 이전/다음 노드를 가리키는 두 개의 포인터 보유</li>
<li>Insertion/Deletion: <strong>O(1)</strong> (위치를 알고 있을 때)</li>
<li>Random Access: <strong>O(n)</strong> (순차 탐색 필요)</li>
</ul>
<p><strong>메모리 구조</strong></p>
<pre><code>Stack:
┌──────────────────┐
│ first (pointer) │ → 첫 번째 노드
│ last (pointer)  │ → 마지막 노드
│ size            │ → 요소 개수
└──────────────────┘

Heap:
┌────┬────┬────┐    ┌────┬────┬────┐    ┌────┬────┬────┐
│prev│data│next│ ↔ │prev│data│next│ ↔ │prev│data│next│
└────┴────┴────┘    └────┴────┴────┘    └────┴────┴────┘</code></pre><ul>
<li>스택에는 <strong>first 포인터</strong>, <strong>last 포인터</strong>, <strong>size 정보</strong> 저장</li>
<li>실제 데이터는 <strong>힙(Heap)</strong>에 동적 할당</li>
</ul>
<p><strong>정렬</strong></p>
<pre><code class="language-cpp">std::list&lt;int&gt; myList = {3, 1, 4, 1, 5};

// ✅ list 전용 sort() 멤버 함수 사용 (O(n log n))
myList.sort();  

// ❌ std::sort() 사용 불가 (Random Access Iterator 필요)
// std::sort(myList.begin(), myList.end());  // 컴파일 에러!</code></pre>
<p><strong>주요 연산 시간 복잡도</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>Insertion/Deletion (위치를 알 때)</td>
<td>O(1)</td>
<td>중간 삽입/삭제 효율적</td>
</tr>
<tr>
<td>Random Access (<code>[]</code>, <code>at()</code>)</td>
<td>불가능</td>
<td>순차 접근만 가능</td>
</tr>
<tr>
<td>Search (find)</td>
<td>O(n)</td>
<td>순차 탐색</td>
</tr>
<tr>
<td>Sort</td>
<td>O(n log n)</td>
<td>멤버 함수 <code>sort()</code> 사용</td>
</tr>
</tbody></table>
<hr>
<h3 id="🔸-forward_list-단일-연결-리스트">🔸 forward_list (단일 연결 리스트)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Singly-Linked List</strong>로 구현</li>
<li>각 노드는 다음 노드만 가리킴 (이전 노드 포인터 없음)</li>
<li><code>list</code>보다 메모리 효율적 (포인터 하나만 필요)</li>
<li>Random Access: <strong>O(n)</strong></li>
</ul>
<p><strong>메모리 구조</strong></p>
<pre><code>Stack:
┌──────────────────┐
│ front (pointer) │ → 첫 번째 노드
└──────────────────┘

Heap:
┌────┬────┐    ┌────┬────┐    ┌────┬────┐
│data│next│ → │data│next│ → │data│next│ → nullptr
└────┴────┘    └────┴────┘    └────┴────┘</code></pre><ul>
<li>스택에는 <strong>front 포인터</strong> 하나만 저장</li>
<li>last 포인터나 size 정보는 저장하지 않음 (메모리 절약)</li>
</ul>
<p><strong>제한 사항</strong></p>
<pre><code class="language-cpp">std::forward_list&lt;int&gt; fList = {1, 2, 3, 4, 5};

// ✅ 가능한 연산
fList.push_front(0);
fList.pop_front();
fList.insert_after(fList.begin(), 10);

// ❌ 불가능한 연산
// fList.push_back(6);   // back 포인터 없음
// fList.size();         // size 저장 안 함 (O(n)으로 계산 가능)
// fList[2];             // Random Access 불가</code></pre>
<hr>
<h3 id="🔸-vector-vs-list-vs-forward_list">🔸 vector vs list vs forward_list</h3>
<p><strong>성능 비교</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>vector</th>
<th>list</th>
<th>forward_list</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Random Access</strong></td>
<td>O(1) ✅</td>
<td>O(n) ❌</td>
<td>O(n) ❌</td>
</tr>
<tr>
<td><strong>Front Insertion/Deletion</strong></td>
<td>O(n)</td>
<td>O(1) ✅</td>
<td>O(1) ✅</td>
</tr>
<tr>
<td><strong>Back Insertion/Deletion</strong></td>
<td>O(1) ✅</td>
<td>O(1) ✅</td>
<td>O(n) ❌</td>
</tr>
<tr>
<td><strong>Middle Insertion/Deletion</strong></td>
<td>O(n)</td>
<td>O(1) ✅</td>
<td>O(1) ✅</td>
</tr>
<tr>
<td><strong>find()</strong></td>
<td>O(n)</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
<tr>
<td><strong>메모리 연속성</strong></td>
<td>연속 ✅</td>
<td>분산</td>
<td>분산</td>
</tr>
<tr>
<td><strong>캐시 효율</strong></td>
<td>높음 ✅</td>
<td>낮음</td>
<td>낮음</td>
</tr>
</tbody></table>
<p><strong>실제 find() 성능</strong></p>
<pre><code class="language-cpp">// 둘 다 O(n)이지만 실제로는 vector가 훨씬 빠름!
std::vector&lt;int&gt; vec(1000000);
std::list&lt;int&gt; lst(1000000);

// vector의 find: 메모리가 연속적 → 캐시 히트율 높음 ✅
auto it1 = std::find(vec.begin(), vec.end(), 999999);

// list의 find: 메모리가 분산 → 캐시 미스 많음 ❌
auto it2 = std::find(lst.begin(), lst.end(), 999999);</code></pre>
<p><strong>왜 vector가 더 빠를까? (캐시 지역성)</strong></p>
<ul>
<li>CPU는 메모리에서 데이터를 가져올 때 <strong>캐시 라인</strong> 단위로 가져옴</li>
<li><strong>vector</strong>: 메모리가 연속 → 한 번에 여러 요소를 캐시에 로드 ✅</li>
<li><strong>list</strong>: 메모리가 분산 → 각 노드마다 캐시 미스 발생 ❌</li>
</ul>
<p><strong>병렬 프로그래밍에서의 문제</strong></p>
<pre><code class="language-cpp">// ❌ list는 병렬화가 어려움
// 각 노드가 분산되어 있어서 동시 접근 시 동기화 오버헤드 큼
std::list&lt;int&gt; lst;

// ✅ vector는 병렬화가 용이함
// 메모리가 연속적이라 분할하기 쉬움
std::vector&lt;int&gt; vec;
#pragma omp parallel for
for (int i = 0; i &lt; vec.size(); i++) {
    // 병렬 처리
}</code></pre>
<p><strong>선택 가이드</strong></p>
<ul>
<li><strong>대부분의 경우</strong>: <code>vector</code> 사용 ✅</li>
<li><strong>중간 삽입/삭제가 빈번</strong>: <code>list</code> 고려</li>
<li><strong>메모리 최소화 + 단방향만 필요</strong>: <code>forward_list</code></li>
</ul>
<hr>
<h3 id="🔸-stack-스택">🔸 stack (스택)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>LIFO (Last In First Out)</strong> 구조</li>
<li>Container Adapter (다른 컨테이너를 감싸서 구현)</li>
<li>기본 컨테이너: <code>deque</code> (변경 가능)</li>
</ul>
<p><strong>지원 컨테이너</strong></p>
<pre><code class="language-cpp">// ✅ 기본 (deque)
std::stack&lt;int&gt; s1;

// ✅ vector로 구현
std::stack&lt;int, std::vector&lt;int&gt;&gt; s2;

// ✅ list로 구현
std::stack&lt;int, std::list&lt;int&gt;&gt; s3;

// ❌ forward_list는 불가 (back 접근 필요)
// std::stack&lt;int, std::forward_list&lt;int&gt;&gt; s4;  // 컴파일 에러!</code></pre>
<p><strong>주요 연산</strong></p>
<pre><code class="language-cpp">std::stack&lt;int&gt; s;

s.push(10);     // O(1) - 삽입
s.pop();        // O(1) - 제거 (값 반환 안 함!)
int top = s.top();  // O(1) - 최상단 접근
bool empty = s.empty();  // O(1)
size_t sz = s.size();    // O(1)</code></pre>
<p><strong>성능 최적화가 필요한 경우</strong></p>
<pre><code class="language-cpp">// 직접 구현 예시 (vector 기반)
template&lt;typename T&gt;
class FastStack {
private:
    std::vector&lt;T&gt; data;
public:
    void push(const T&amp; value) {
        data.push_back(value);
    }
    void pop() {
        data.pop_back();
    }
    T&amp; top() {
        return data.back();
    }
    // ...
};</code></pre>
<hr>
<h3 id="🔸-queue-큐">🔸 queue (큐)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>FIFO (First In First Out)</strong> 구조</li>
<li>Container Adapter</li>
<li>기본 컨테이너: <code>deque</code> (변경 가능)</li>
</ul>
<p><strong>지원 컨테이너</strong></p>
<pre><code class="language-cpp">// ✅ 기본 (deque)
std::queue&lt;int&gt; q1;

// ✅ list로 구현
std::queue&lt;int, std::list&lt;int&gt;&gt; q2;

// ❌ vector는 불가 (front에서 pop이 O(n))
// std::queue&lt;int, std::vector&lt;int&gt;&gt; q3;  // 컴파일 에러!

// ❌ forward_list는 불가 (back 접근 필요)
// std::queue&lt;int, std::forward_list&lt;int&gt;&gt; q4;  // 컴파일 에러!</code></pre>
<p><strong>주요 연산</strong></p>
<pre><code class="language-cpp">std::queue&lt;int&gt; q;

q.push(10);     // O(1) - 뒤에 삽입
q.pop();        // O(1) - 앞에서 제거
int front = q.front();  // O(1) - 앞 요소 접근
int back = q.back();    // O(1) - 뒤 요소 접근
bool empty = q.empty(); // O(1)
size_t sz = q.size();   // O(1)</code></pre>
<hr>
<h3 id="🔸-priority_queue-우선순위-큐">🔸 priority_queue (우선순위 큐)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><strong>Max Heap</strong>으로 구현 (기본값)</li>
<li>가장 큰 원소가 항상 top에 위치</li>
<li>Container Adapter (기본: <code>vector</code>)</li>
</ul>
<p><strong>시간 복잡도</strong></p>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td>Insertion (<code>push</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Extraction (<code>pop</code>)</td>
<td>O(log n)</td>
</tr>
<tr>
<td>Top 접근 (<code>top</code>)</td>
<td>O(1) ✅</td>
</tr>
</tbody></table>
<p><strong>힙 구조 (배열 기반)</strong></p>
<pre><code>인덱스:  0   1   2   3   4   5   6
값:    [100, 50, 80, 30, 20, 60, 70]

트리 구조:
           100 (idx=0)
          /   \
        50     80
       /  \   /  \
      30  20 60  70

부모-자식 관계:
- 부모 노드: (idx - 1) / 2
- 왼쪽 자식: 2 * idx + 1
- 오른쪽 자식: 2 * idx + 2</code></pre><p><strong>힙 속성</strong></p>
<ul>
<li><strong>Max Heap</strong>: 부모 노드 ≥ 자식 노드</li>
<li><strong>Min Heap</strong>: 부모 노드 ≤ 자식 노드</li>
</ul>
<p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">#include &lt;queue&gt;

// ✅ Max Heap (기본)
std::priority_queue&lt;int&gt; maxHeap;
maxHeap.push(30);
maxHeap.push(100);
maxHeap.push(50);
std::cout &lt;&lt; maxHeap.top();  // 100

// ✅ Min Heap (greater 사용)
std::priority_queue&lt;int, std::vector&lt;int&gt;, std::greater&lt;int&gt;&gt; minHeap;
minHeap.push(30);
minHeap.push(100);
minHeap.push(50);
std::cout &lt;&lt; minHeap.top();  // 30

// ✅ 커스텀 비교 함수
auto cmp = [](int a, int b) { return a &gt; b; };  // Min Heap
std::priority_queue&lt;int, std::vector&lt;int&gt;, decltype(cmp)&gt; customHeap(cmp);</code></pre>
<p><strong>인덱스 계산 예시</strong></p>
<pre><code class="language-cpp">// 인덱스 3의 부모: (3 - 1) / 2 = 1
// 인덱스 1의 왼쪽 자식: 2 * 1 + 1 = 3
// 인덱스 1의 오른쪽 자식: 2 * 1 + 2 = 4</code></pre>
<hr>
<h3 id="🔸-heap-알고리즘-algorithm-library">🔸 heap 알고리즘 (Algorithm Library)</h3>
<p><strong>기본 특징</strong></p>
<ul>
<li><code>&lt;algorithm&gt;</code> 헤더에 포함된 힙 관련 함수들</li>
<li>기존 컨테이너(주로 <code>vector</code>)를 힙으로 변환/관리</li>
<li><code>priority_queue</code>와 동일한 내부 구조</li>
</ul>
<p><strong>주요 함수</strong></p>
<table>
<thead>
<tr>
<th>함수</th>
<th>시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>make_heap</code></td>
<td>O(n)</td>
<td>범위를 힙으로 변환</td>
</tr>
<tr>
<td><code>push_heap</code></td>
<td>O(log n)</td>
<td>마지막 요소를 힙에 삽입</td>
</tr>
<tr>
<td><code>pop_heap</code></td>
<td>O(log n)</td>
<td>최대값을 마지막으로 이동</td>
</tr>
</tbody></table>
<p><strong>사용 예시</strong></p>
<pre><code class="language-cpp">#include &lt;algorithm&gt;
#include &lt;vector&gt;

std::vector&lt;int&gt; vec = {30, 100, 50, 20, 80};

// ✅ 힙 생성 - O(n)
std::make_heap(vec.begin(), vec.end());
// vec: [100, 80, 50, 20, 30] (Max Heap)

// ✅ 최대값 확인
std::cout &lt;&lt; vec.front();  // 100 (O(1))

// ✅ 최대값 제거
std::pop_heap(vec.begin(), vec.end());  // O(log n)
// 최대값을 맨 뒤로 이동
// vec: [80, 30, 50, 20, 100]
vec.pop_back();  // 실제 제거 - O(1)
// vec: [80, 30, 50, 20]

// ✅ 새 요소 추가
vec.push_back(90);  // O(1)
std::push_heap(vec.begin(), vec.end());  // O(log n)
// vec: [90, 80, 50, 20, 30]</code></pre>
<p><strong>make_heap이 O(n)인 이유</strong></p>
<pre><code>Naive 방식 (위에서 아래로): O(n log n)
- 각 요소를 하나씩 삽입: n번
- 각 삽입마다 heapify: O(log n)
- 총 시간: O(n log n)

Floyd 알고리즘 (아래에서 위로): O(n) ✅
- 리프 노드는 이미 힙 속성 만족
- 아래에서 위로 올라가며 heapify
- 높이 h인 노드 개수: n / 2^(h+1)
- 각 노드의 작업량: O(h)
- 총 시간: Σ(n / 2^(h+1) * h) = O(n)</code></pre><p><strong>priority_queue vs heap 알고리즘</strong></p>
<pre><code class="language-cpp">// priority_queue: 편리하지만 유연성 낮음
std::priority_queue&lt;int&gt; pq;
pq.push(10);
// 내부 데이터 직접 접근 불가

// heap 알고리즘: 유연하지만 수동 관리 필요
std::vector&lt;int&gt; vec = {10, 20, 30};
std::make_heap(vec.begin(), vec.end());
// 벡터에 직접 접근 가능 ✅
vec[0];  // 최대값 확인</code></pre>
<p><strong>언제 heap 알고리즘을 사용할까?</strong></p>
<ul>
<li>기존 컨테이너를 힙으로 변환해야 할 때</li>
<li>힙 내부 데이터에 직접 접근이 필요할 때</li>
<li>부분 정렬이 필요할 때 (Top K 문제 등)</li>
</ul>
<hr>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>컨테이너 선택 가이드</strong></p>
<ol>
<li><strong>대부분의 경우</strong>: <code>vector</code> 사용 (캐시 효율, 랜덤 액세스)</li>
<li><strong>중간 삽입/삭제 빈번</strong>: <code>list</code> 고려</li>
<li><strong>메모리 최소화</strong>: <code>forward_list</code></li>
<li><strong>LIFO 필요</strong>: <code>stack</code></li>
<li><strong>FIFO 필요</strong>: <code>queue</code></li>
<li><strong>우선순위 관리</strong>: <code>priority_queue</code></li>
<li><strong>힙 직접 제어</strong>: heap 알고리즘</li>
</ol>
<p><strong>성능 최적화 팁</strong></p>
<ul>
<li>대부분의 경우 <strong>vector가 가장 빠름</strong> (캐시 지역성)</li>
<li>병렬 프로그래밍에서는 <strong>vector</strong> 사용 권장</li>
<li>성능이 매우 중요하면 직접 구현 고려</li>
<li>힙 생성은 <code>make_heap</code>이 삽입보다 빠름 (O(n) vs O(n log n))</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/container">C++ Standard Containers</a>
<a href="https://en.cppreference.com/w/cpp/container/list">std::list</a>
<a href="https://en.cppreference.com/w/cpp/container/forward_list">std::forward_list</a>
<a href="https://en.cppreference.com/w/cpp/container/priority_queue">std::priority_queue</a>
<a href="https://en.cppreference.com/w/cpp/algorithm#Heap_operations">Heap Algorithms</a>
<a href="https://en.cppreference.com/w/cpp/container#Container_adaptors">Container Adapters</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 문자열 (string, string_view)]]></title>
            <link>https://velog.io/@sehee-jj/C-%EB%AC%B8%EC%9E%90%EC%97%B4-string-stringview</link>
            <guid>https://velog.io/@sehee-jj/C-%EB%AC%B8%EC%9E%90%EC%97%B4-string-stringview</guid>
            <pubDate>Sat, 17 Jan 2026 13:45:28 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-문자열-string-string_view">📚 C++ 문자열 (string, string_view)</h2>
<h3 id="🔸-c-style-문자열의-메모리-위치">🔸 C-Style 문자열의 메모리 위치</h3>
<p><strong>char 배열 vs char 포인터</strong></p>
<pre><code class="language-cpp">int main()
{
    char a[] = &quot;hello&quot;;      // 스택에 할당
    char* b = &quot;hello&quot;;       // 포인터는 스택, 문자열은 read-only 영역

    // 수정 테스트
    a[0] = &#39;d&#39;;              // ✅ 성공: &quot;dello&quot;
    // b[0] = &#39;d&#39;;           // ❌ Segmentation Fault!

    return 0;
}</code></pre>
<p><strong>메모리 레이아웃</strong></p>
<pre><code>스택 (Stack):
┌──────────────┐
│ a[] 배열     │
│ &#39;h&#39;&#39;e&#39;&#39;l&#39;&#39;l&#39; │
│ &#39;o&#39; &#39;\0&#39;     │
└──────────────┘
  ↑ 수정 가능

스택 (Stack):
┌──────────────┐
│ b (포인터)   │
│ 0x400000 ────┼───→ Read-Only Data Segment:
└──────────────┘     ┌──────────────────┐
                    │ &quot;hello\0&quot;       │
                    └──────────────────┘
                      ↑ 수정 불가능!</code></pre><p>문자열 리터럴은 read-only data segment에 저장되며, 수정 시도 시 segmentation fault 발생</p>
<p><strong>const char*에는 반드시 const 사용</strong></p>
<pre><code class="language-cpp">// ❌ 나쁜 코드: 경고 발생 (C++11 이후 deprecated)
char* b = &quot;hello&quot;;
b[0] = &#39;d&#39;;  // Undefined Behavior (대부분 Segmentation Fault)

// ✅ 좋은 코드: const로 명시
const char* b = &quot;hello&quot;;  // 수정 불가능함을 명시
// b[0] = &#39;d&#39;;  // 컴파일 에러로 실수 방지!</code></pre>
<h3 id="🔸-stdstring의-메모리-할당">🔸 std::string의 메모리 할당</h3>
<p><strong>Small String Optimization (SSO)</strong></p>
<p>std::string은 항상 힙에 할당되는 것이 아니라, <strong>SSO</strong>를 통해 짧은 문자열은 스택에, 긴 문자열만 힙에 저장</p>
<pre><code class="language-cpp">#include &lt;string&gt;

int main()
{
    // 짧은 문자열 (SSO 적용, 보통 15-22자 이하)
    std::string short_str = &quot;hello&quot;;
    // → 스택에 저장 (힙 할당 없음)

    // 긴 문자열 (SSO 한계 초과)
    std::string long_str = &quot;This is a very long string that exceeds SSO&quot;;
    // → 힙에 동적 할당

    return 0;
}</code></pre>
<p><strong>SSO 동작 방식</strong></p>
<pre><code>std::string 객체 (스택):
┌────────────────────────┐
│ 포인터 / 데이터        │ ← SSO 버퍼 (15-22자)
│ 길이                  │
│ 용량 / SSO 플래그      │
└────────────────────────┘

짧은 문자열 (&quot;hello&quot;):
┌─────────────────────────┐
│ &quot;hello\0&quot;... (여유공간)│ ← 스택 내부에 직접 저장
│ 길이: 5                │
│ 플래그: SSO 사용 중     │
└─────────────────────────┘

긴 문자열:
┌───────────────┐
│ 포인터 ───────┼───→ 힙 메모리
│ 길이: 50     │    &quot;This is a very long...&quot;
│ 용량: 64     │
└───────────────┘</code></pre><p>구현에 따라 GCC/MSVC는 15자, Clang은 22자까지 SSO 적용</p>
<h3 id="🔸-stdstring_view-c17">🔸 std::string_view (C++17)</h3>
<p><strong>std::string_view란?</strong></p>
<ul>
<li><code>std::span</code>과 비슷한 개념</li>
<li>포인터 + 길이 정보만 저장 (16 bytes)</li>
<li>소유권 없이 문자열을 참조만 함</li>
</ul>
<p><strong>메모리 구조</strong></p>
<pre><code class="language-cpp">template&lt;typename CharT&gt;
class basic_string_view
{
    const CharT* ptr;   // 데이터 시작 주소 (8 bytes)
    size_t length;      // 문자열 길이 (8 bytes)
};

// sizeof(std::string_view) == 16 bytes</code></pre>
<h3 id="🔸-함수-파라미터로-문자열-전달하기">🔸 함수 파라미터로 문자열 전달하기</h3>
<p><strong>const std::string&amp; 사용 시 문제</strong></p>
<pre><code class="language-cpp">void print(const std::string&amp; str)
{
    std::cout &lt;&lt; str &lt;&lt; std::endl;
}

int main()
{
    const char* c_str = &quot;hello&quot;;

    // ❌ 비효율: 임시 std::string 객체 생성!
    print(c_str);

    // 실제 동작:
    // 1. 임시 std::string tmp(c_str) 생성
    // 2. 힙 메모리 할당 (SSO 초과 시)
    // 3. &quot;hello&quot; 복사
    // 4. print(tmp) 호출
    // 5. tmp 소멸, 메모리 해제

    return 0;
}</code></pre>
<p>const char*나 문자열 리터럴을 const std::string&amp; 파라미터로 전달하면 임시 std::string 객체가 생성되어 메모리 할당과 복사 발생</p>
<p><strong>std::string_view 사용</strong></p>
<pre><code class="language-cpp">void print(std::string_view str)  // ✅ 효율적!
{
    std::cout &lt;&lt; str &lt;&lt; std::endl;
}

int main()
{
    // 모두 임시 객체 생성 없이 전달 가능

    // C 문자열 배열
    char arr[] = &quot;hello&quot;;
    print(arr);  // 포인터 + 길이만 전달

    // const char* 포인터
    const char* c_str = &quot;world&quot;;
    print(c_str);  // 포인터 + 길이만 전달

    // std::string
    std::string cpp_str = &quot;string&quot;;
    print(cpp_str);  // string의 내부 포인터 + 길이만 전달

    return 0;
}</code></pre>
<p>string_view는 포인터와 길이만 저장하므로 임시 객체 생성이나 메모리 할당 없이 전달 가능</p>
<p><strong>성능 차이</strong></p>
<pre><code class="language-cpp">// const std::string&amp; 사용
void old_print(const std::string&amp; str) { }

old_print(&quot;hello&quot;);
// → 임시 std::string 생성
// → 힙 할당 (SSO 초과 시)
// → 메모리 복사
// → 함수 호출
// → 소멸 및 메모리 해제

// std::string_view 사용
void new_print(std::string_view str) { }

new_print(&quot;hello&quot;);
// → 포인터와 길이만 전달 (16 bytes 복사)
// → 끝!</code></pre>
<p>벤치마크 결과 string_view 사용 시 문자열 리터럴 전달에서 100배 이상 성능 향상 가능</p>
<h3 id="🔸-stdstring_view-주의사항">🔸 std::string_view 주의사항</h3>
<p><strong>Dangling Reference</strong></p>
<pre><code class="language-cpp">std::string_view get_view()
{
    std::string temp = &quot;temporary&quot;;
    return std::string_view(temp);  // ❌ 위험!
}  // temp 소멸

int main()
{
    auto view = get_view();
    std::cout &lt;&lt; view &lt;&lt; std::endl;  // ❌ Undefined Behavior

    return 0;
}</code></pre>
<p><strong>std::string 재할당 시 무효화</strong></p>
<pre><code class="language-cpp">std::string str = &quot;short&quot;;
std::string_view view = str;

std::cout &lt;&lt; view &lt;&lt; std::endl;  // ✅ &quot;short&quot;

str = &quot;This is a very long string that causes reallocation&quot;;
// → str의 내부 버퍼가 재할당됨

std::cout &lt;&lt; view &lt;&lt; std::endl;  // ❌ Undefined Behavior
                                 // view는 여전히 옛날 주소를 가리킴</code></pre>
<p><strong>임시 객체 바인딩</strong></p>
<pre><code class="language-cpp">void process(std::string_view view)
{
    // view 사용...
}

int main()
{
    std::string get_string() { return &quot;temp&quot;; }

    // ❌ 위험: 임시 객체의 view
    process(get_string());  // get_string() 반환 후 즉시 소멸

    // ✅ 안전: 수명 연장
    std::string str = get_string();
    process(str);

    return 0;
}</code></pre>
<p>string_view는 임시 객체도 받아들이므로, 수명 관리에 주의 필요</p>
<h3 id="🔸-char-const-char-stdstring-비교">🔸 char[], const char*, std::string 비교</h3>
<table>
<thead>
<tr>
<th>타입</th>
<th>메모리 위치</th>
<th>수정 가능</th>
<th>크기 정보</th>
<th>메모리 관리</th>
</tr>
</thead>
<tbody><tr>
<td><code>char[]</code></td>
<td>스택</td>
<td>✅</td>
<td>컴파일 타임</td>
<td>자동</td>
</tr>
<tr>
<td><code>const char*</code></td>
<td>리터럴: read-only<br>동적: 힙</td>
<td>리터럴: ❌<br>동적: ✅</td>
<td>❌ (strlen 필요)</td>
<td>리터럴: 자동<br>동적: 수동</td>
</tr>
<tr>
<td><code>std::string</code></td>
<td>SSO: 스택<br>긴 문자열: 힙</td>
<td>✅</td>
<td>✅ (O(1))</td>
<td>자동 (RAII)</td>
</tr>
<tr>
<td><code>std::string_view</code></td>
<td>원본 참조</td>
<td>❌ (읽기 전용)</td>
<td>✅ (O(1))</td>
<td>소유권 없음</td>
</tr>
</tbody></table>
<h3 id="🔸-함수-파라미터-선택-가이드">🔸 함수 파라미터 선택 가이드</h3>
<pre><code class="language-cpp">// 읽기 전용, 다양한 타입 받기
void read_only(std::string_view str)  // ✅ 권장 (C++17 이상)
{
    std::cout &lt;&lt; str &lt;&lt; std::endl;
}

// std::string만 받기
void string_only(const std::string&amp; str)
{
    std::cout &lt;&lt; str.size() &lt;&lt; std::endl;
}

// 소유권 획득
void take_ownership(std::string str)  // 복사 또는 이동
{
    // str을 멤버 변수에 저장하거나 수정
}

// 수정이 필요한 경우
void modify(std::string&amp; str)
{
    str += &quot; modified&quot;;
}

// C 라이브러리와 호환 필요
void c_compat(const char* str)
{
    printf(&quot;%s\n&quot;, str);  // C 함수 사용
}</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>char 배열 vs char 포인터</strong></p>
<ul>
<li><code>char a[] = &quot;hello&quot;</code>: 스택에 복사본 생성, 수정 가능</li>
<li><code>const char* b = &quot;hello&quot;</code>: read-only 영역 참조, 수정 불가 (const 필수)</li>
</ul>
<p><strong>std::string의 메모리</strong></p>
<ul>
<li><strong>짧은 문자열 (15-22자)</strong>: 스택 (SSO)</li>
<li><strong>긴 문자열</strong>: 힙 동적 할당</li>
<li>자동 메모리 관리 (RAII)</li>
</ul>
<p><strong>std::string_view</strong></p>
<ul>
<li>포인터 + 길이만 저장 (16 bytes)</li>
<li><code>const char*</code>, <code>std::string</code> 모두 임시 객체 없이 전달</li>
<li>읽기 전용, 소유권 없음</li>
<li><strong>주의</strong>: 원본 수명 관리 필요</li>
</ul>
<p><strong>함수 파라미터 권장사항 (C++17 이상)</strong></p>
<ul>
<li><strong>읽기 전용</strong>: <code>std::string_view</code></li>
<li><strong>수정 필요</strong>: <code>std::string&amp;</code></li>
<li><strong>소유권 획득</strong>: <code>std::string</code> (값 전달)</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://shaharmike.com/cpp/std-string/">C++ std::string Implementation</a>
<a href="https://en.cppreference.com/w/cpp/string/basic_string_view">C++ std::string_view</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 배열과 컨테이너 (Array, Deque, Span)]]></title>
            <link>https://velog.io/@sehee-jj/C-%EB%B0%B0%EC%97%B4%EA%B3%BC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-Array-Deque-Span</link>
            <guid>https://velog.io/@sehee-jj/C-%EB%B0%B0%EC%97%B4%EA%B3%BC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-Array-Deque-Span</guid>
            <pubDate>Sat, 17 Jan 2026 13:29:04 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-배열과-컨테이너-array-deque-span">📚 C++ 배열과 컨테이너 (Array, Deque, Span)</h2>
<h3 id="🔸-배열과-컨테이너-비교">🔸 배열과 컨테이너 비교</h3>
<p><strong>5가지 메모리 관리 방법 비교</strong></p>
<pre><code class="language-cpp">#include &lt;array&gt;
#include &lt;vector&gt;

int main()
{
    // 1. C 정적 배열
    int cArray[5] = {1, 2, 3, 4, 5};  
    // 스택, 컴파일 타임 크기, 자동 관리

    // 2. C++ 동적 배열 (new/delete)
    int* cppDynArray = new int[5]{1, 2, 3, 4, 5};
    // 힙, 런타임 크기, 수동 할당/해제
    // ✅ 타입 안전
    // ❌ 메모리 누수 위험
    delete[] cppDynArray;

    // 3. std::array
    std::array&lt;int, 5&gt; stdArray{1, 2, 3, 4, 5};  
    // 스택, 컴파일 타임 크기, 자동 관리
    // ✅ 타입 안전, 크기 정보 유지

    // 4. std::vector (권장!)
    std::vector&lt;int&gt; vec{1, 2, 3, 4, 5};  
    // 힙, 런타임 크기, 자동 관리
    // ✅ 동적 크기 조정
    // ✅ 메모리 자동 해제
    // ✅ 타입 안전

    return 0;
}</code></pre>
<p><strong>메모리 할당 위치 정리</strong></p>
<table>
<thead>
<tr>
<th>타입</th>
<th>메모리 위치</th>
<th>크기 결정</th>
<th>관리 방식</th>
<th>안전성</th>
</tr>
</thead>
<tbody><tr>
<td>C 배열</td>
<td>스택</td>
<td>컴파일 타임</td>
<td>자동</td>
<td>⚠️</td>
</tr>
<tr>
<td>C++ 동적 배열 (new)</td>
<td>힙</td>
<td>런타임</td>
<td>수동</td>
<td>⚠️</td>
</tr>
<tr>
<td>std::array</td>
<td>스택</td>
<td>컴파일 타임</td>
<td>자동</td>
<td>✅</td>
</tr>
<tr>
<td>std::vector</td>
<td>힙</td>
<td>런타임</td>
<td>자동</td>
<td>✅</td>
</tr>
</tbody></table>
<p><strong>왜 std::vector를 사용해야 하는가?</strong></p>
<pre><code class="language-cpp">// ❌ C++ 동적 배열의 문제점
int* arr2 = new int[5];
// delete[] 깜빡하면 메모리 누수
// 크기 변경 불가
delete[] arr2;

// ✅ std::vector의 장점
std::vector&lt;int&gt; vec(5);
// 자동 메모리 관리
// 크기 정보 포함 (vec.size())
// 동적 크기 조정 (vec.push_back())
// 타입 안전
// RAII (자동 소멸)</code></pre>
<h3 id="🔸-stdarray">🔸 std::array</h3>
<p><strong>std::array란?</strong></p>
<ul>
<li>컴파일 타임에 크기가 고정된 배열</li>
<li>스택 메모리에 할당</li>
<li>C 스타일 배열을 안전하게 감싼 래퍼</li>
</ul>
<p><strong>std::array의 장점</strong></p>
<pre><code class="language-cpp">std::array&lt;int, 5&gt; arr{1, 2, 3, 4, 5};

// 경계 검사
arr.at(10);  // std::out_of_range 예외 발생

// 크기 확인
std::cout &lt;&lt; arr.size() &lt;&lt; std::endl;  // 5

// 반복자 사용
for (auto it = arr.begin(); it != arr.end(); ++it)
{
    std::cout &lt;&lt; *it &lt;&lt; &quot; &quot;;
}

// Range-based for
for (const auto&amp; elem : arr)
{
    std::cout &lt;&lt; elem &lt;&lt; &quot; &quot;;
}</code></pre>
<p><strong>std::array vs C 배열</strong></p>
<pre><code class="language-cpp">// C 배열
int cArr[5];
// sizeof(cArr) == 20 (5 * 4)
// 함수에 전달 시 포인터로 decay
// 크기 정보 손실

void func(int arr[])  // 실제로는 int*
{
    // sizeof(arr) == 8 (포인터 크기)
}

// std::array
std::array&lt;int, 5&gt; stdArr;
// sizeof(stdArr) == 20 (5 * 4)
// 함수에 전달 시 크기 정보 유지

void func(std::array&lt;int, 5&gt;&amp; arr)
{
    // sizeof(arr) == 20
    // arr.size() == 5
}</code></pre>
<h3 id="🔸-다차원-배열과-벡터">🔸 다차원 배열과 벡터</h3>
<p><strong>메모리 레이아웃 차이</strong></p>
<p><strong>1. 다차원 배열 (스택, 연속된 메모리)</strong></p>
<pre><code class="language-cpp">int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 메모리 레이아웃 (연속적):
// [1][2][3][4][5][6][7][8][9][10][11][12]
//  ↑ 스택에 한 번에 할당</code></pre>
<p><strong>2. 다차원 벡터 (힙, 분산된 메모리)</strong></p>
<pre><code class="language-cpp">std::vector&lt;std::vector&lt;int&gt;&gt; vec = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 메모리 레이아웃 (비연속적):
// vec (스택):
// [ptr1][ptr2][ptr3]
//   ↓     ↓     ↓
// 힙:
// [1][2][3][4]  (첫 번째 행)
// [5][6][7][8]  (두 번째 행)
// [9][10][11][12]  (세 번째 행)</code></pre>
<p><strong>연속된 메모리를 사용하는 Wrapper</strong></p>
<p>벡터를 사용하되 연속된 메모리를 유지하려면 1차원 벡터로 관리하고 인덱스 변환을 사용:</p>
<pre><code class="language-cpp">class Matrix
{
public:
    Matrix(size_t rows, size_t cols)
        : mRows{rows}, mCols{cols}, mData(rows * cols)
    {
    }

    int&amp; operator()(size_t row, size_t col)
    {
        return mData[row * mCols + col];  // 2D → 1D 인덱스 변환
    }

    const int&amp; operator()(size_t row, size_t col) const
    {
        return mData[row * mCols + col];
    }

private:
    size_t mRows;
    size_t mCols;
    std::vector&lt;int&gt; mData;  // 1차원 벡터로 연속 메모리 보장
};

int main()
{
    Matrix mat(3, 4);

    // 사용
    mat(0, 0) = 1;
    mat(0, 1) = 2;
    mat(1, 0) = 5;

    return 0;
}</code></pre>
<h3 id="🔸-캐시-최적화-cache-hitmiss">🔸 캐시 최적화 (Cache Hit/Miss)</h3>
<p><strong>캐시 라인 (Cache Line)</strong></p>
<ul>
<li>CPU 캐시의 최소 단위 (보통 64 bytes)</li>
<li>메모리를 읽을 때 캐시 라인 단위로 가져옴</li>
<li>연속된 메모리는 캐시에 함께 로드됨</li>
</ul>
<p><strong>잘못된 루프 (Cache Miss 많음)</strong></p>
<pre><code class="language-cpp">const size_t ROWS = 1000;
const size_t COLS = 1000;
int arr[ROWS][COLS];

// ❌ 열 우선 순회 (Column-major)
for (size_t col = 0; col &lt; COLS; col++)
{
    for (size_t row = 0; row &lt; ROWS; row++)
    {
        arr[row][col] = 0;  // 메모리 점프 발생!
    }
}</code></pre>
<p><strong>메모리 접근 패턴:</strong></p>
<pre><code>arr[0][0] → arr[1][0] → arr[2][0] → ...
               ↓         ↓ (COLS * 4 bytes 떨어짐)
            캐시 미스!</code></pre><p><strong>올바른 루프 (Cache Hit 많음)</strong></p>
<pre><code class="language-cpp">// ✅ 행 우선 순회 (Row-major)
for (size_t row = 0; row &lt; ROWS; row++)
{
    for (size_t col = 0; col &lt; COLS; col++)
    {
        arr[row][col] = 0;  // 연속된 메모리 접근!
    }
}</code></pre>
<p><strong>메모리 접근 패턴:</strong></p>
<pre><code>arr[0][0] → arr[0][1] → arr[0][2] → ...
                ↓         ↓ (4 bytes 떨어짐, 같은 캐시 라인)
             캐시 히트!</code></pre><p><strong>핵심 규칙: Inner Loop는 Cache Line에 맞춰라</strong></p>
<ul>
<li>C/C++ 배열은 row-major 순서로 저장됨</li>
<li>Inner loop에서 연속된 메모리를 접근하도록 설계</li>
<li>열(column) 순회를 inner loop에 배치</li>
</ul>
<h3 id="🔸-stddeque-double-ended-queue">🔸 std::deque (Double-Ended Queue)</h3>
<p><strong>std::deque란?</strong></p>
<ul>
<li>양쪽 끝에서 삽입/삭제가 O(1)인 컨테이너</li>
<li>실무에서는 거의 사용하지 않음</li>
<li>Vector와 List의 중간 형태</li>
</ul>
<p><strong>std::deque vs std::vector</strong></p>
<pre><code class="language-cpp">#include &lt;deque&gt;
#include &lt;vector&gt;

std::vector&lt;int&gt; vec;
vec.push_back(1);   // 뒤에 추가: O(1) (amortized)
// vec.push_front(0);  // ❌ vector는 push_front 없음

std::deque&lt;int&gt; deq;
deq.push_back(1);   // 뒤에 추가: O(1)
deq.push_front(0);  // ✅ 앞에 추가: O(1)</code></pre>
<p><strong>deque의 메모리 구조 (Chunk Array)</strong></p>
<pre><code>Deque:
┌─────────────────────┐
│ Chunk 포인터들      │ (중앙 배열)
│ [ptr1][ptr2][ptr3] │
└────┬─────┬─────┬────┘
     ↓     ↓     ↓
   청크   청크   청크 (힙)
[▢▢▢▢][■■■■][▢▢▢▢]
           ↑
         데이터</code></pre><p><strong>Vector의 재할당 vs Deque의 확장</strong></p>
<p><strong>Vector:</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; vec{1, 2, 3, 4};
// 메모리: [1][2][3][4] (capacity: 4)

vec.push_back(5);  // capacity 부족!
// → 새 공간 할당 (capacity: 8)
// → 기존 데이터 전체 복사/이동
// → 기존 공간 해제</code></pre>
<p><strong>Deque:</strong></p>
<pre><code class="language-cpp">std::deque&lt;int&gt; deq{1, 2, 3, 4};
// Chunk1: [1][2][3][4]

deq.push_back(5);  // 현재 청크가 가득 찬 경우
// → 새 청크만 할당
// → 기존 데이터는 그대로!
// Chunk1: [1][2][3][4]
// Chunk2: [5][▢][▢][▢]</code></pre>
<p><strong>Deque의 장점</strong></p>
<ol>
<li><code>push_front()</code> / <code>push_back()</code> 모두 O(1)</li>
<li>재할당 시 기존 데이터를 복사/이동하지 않음</li>
<li>Iterator invalidation이 적음 (중간 삽입 제외)</li>
</ol>
<p><strong>Deque의 단점</strong></p>
<ol>
<li><p><strong>두 번의 포인터 역참조 필요</strong></p>
<pre><code class="language-cpp">deq[i];
// 1. 어느 청크인지 계산
// 2. 청크 포인터 배열에서 청크 주소 읽기
// 3. 청크 내에서 오프셋 계산
// 4. 실제 데이터 접근</code></pre>
</li>
<li><p><strong>캐시 미스 가능성</strong></p>
<ul>
<li>청크들이 메모리상 분산되어 있음</li>
<li>연속된 접근 시 캐시 효율 떨어짐</li>
</ul>
</li>
<li><p><strong>메모리 오버헤드</strong></p>
<ul>
<li>청크 포인터 배열 관리</li>
<li>각 청크마다 약간의 낭비 공간</li>
</ul>
</li>
</ol>
<p><strong>성능 비교</strong></p>
<pre><code class="language-cpp">// 랜덤 접근
vec[1000];   // O(1), 빠름 (한 번의 역참조)
deq[1000];   // O(1), 느림 (두 번의 역참조)

// 앞쪽 삽입
// vec.push_front()  // 없음 (구현하려면 O(n))
deq.push_front(x);   // O(1)

// 뒤쪽 삽입
vec.push_back(x);    // O(1) amortized
deq.push_back(x);    // O(1)</code></pre>
<p><strong>언제 Deque를 사용할까?</strong></p>
<ul>
<li>양쪽 끝에서 삽입/삭제가 빈번할 때</li>
<li>중간 접근은 드물 때</li>
<li>하지만 대부분의 경우 <code>std::vector</code>가 더 나음</li>
</ul>
<h3 id="🔸-push_back-vs-emplace_back">🔸 push_back vs emplace_back</h3>
<p><strong>기본 차이</strong></p>
<pre><code class="language-cpp">struct Student {
    std::string name;
    int age;
    Student(std::string n, int a) : name(n), age(a) {
        std::cout &lt;&lt; &quot;생성자 호출\n&quot;;
    }
    Student(const Student&amp; other) : name(other.name), age(other.age) {
        std::cout &lt;&lt; &quot;복사 생성자 호출\n&quot;;
    }
    Student(Student&amp;&amp; other) : name(std::move(other.name)), age(other.age) {
        std::cout &lt;&lt; &quot;이동 생성자 호출\n&quot;;
    }
};

std::vector&lt;Student&gt; v;

// push_back: 임시 객체 생성 → 이동 → 소멸
v.push_back(Student(&quot;Alice&quot;, 20));
// 출력: 생성자 호출 → 이동 생성자 호출

// emplace_back: 컨테이너 내부에서 직접 생성
v.emplace_back(&quot;Bob&quot;, 21);
// 출력: 생성자 호출 (끝)</code></pre>
<p><strong>언제 emplace_back이 확실히 유리한가?</strong></p>
<p><strong>1. 복사/이동이 비용이 큰 경우</strong></p>
<pre><code class="language-cpp">struct BigData {
    std::vector&lt;int&gt; data;
    BigData(int size) : data(size, 42) {}
};

std::vector&lt;BigData&gt; v;

// push_back: 생성 → 이동 (vector 내부 데이터도 이동)
v.push_back(BigData(10000));

// emplace_back: 바로 생성 (이동 없음)
v.emplace_back(10000);  // ✅ 더 효율적</code></pre>
<p><strong>2. 복사 생성자가 delete된 경우</strong></p>
<pre><code class="language-cpp">struct NonCopyable {
    NonCopyable(int x) {}
    NonCopyable(const NonCopyable&amp;) = delete;
    NonCopyable(NonCopyable&amp;&amp;) = delete;
};

std::vector&lt;NonCopyable&gt; v;

// v.push_back(NonCopyable(42));  // ❌ 컴파일 에러
v.emplace_back(42);               // ✅ 작동</code></pre>
<p><strong>3. 생성자 인자가 여러 개일 때 (코드 간결성)</strong></p>
<pre><code class="language-cpp">struct Point3D {
    double x, y, z;
    Point3D(double x, double y, double z) : x(x), y(y), z(z) {}
};

std::vector&lt;Point3D&gt; points;

// push_back: 임시 객체 명시적 생성
points.push_back(Point3D(1.0, 2.0, 3.0));

// emplace_back: 인자만 전달 (더 간결)
points.emplace_back(1.0, 2.0, 3.0);</code></pre>
<p><strong>C++17 이후의 현실: Copy Elision</strong></p>
<pre><code class="language-cpp">std::vector&lt;Student&gt; v;

// C++17 이후: 컴파일러가 임시 객체 생성을 최적화
v.push_back(Student(&quot;Alice&quot;, 20));
// → 실제로는 vector 내부에 바로 생성될 수 있음 (RVO)

v.emplace_back(&quot;Alice&quot;, 20);
// → 거의 동일한 코드로 컴파일됨</code></pre>
<p><strong>간단한 타입에서는 차이가 거의 없음:</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; v;

v.push_back(42);     // 성능 차이 없음
v.emplace_back(42);  // 성능 차이 없음</code></pre>
<p><strong>주의사항: explicit 생성자</strong></p>
<pre><code class="language-cpp">struct Widget {
    explicit Widget(int x) {}
};

std::vector&lt;Widget&gt; v;

// v.push_back(42);     // ❌ 컴파일 에러 (explicit 생성자)
v.emplace_back(42);     // ✅ 작동 (의도치 않은 변환 가능)</code></pre>
<p><strong>실전 가이드</strong></p>
<pre><code class="language-cpp">// ✅ emplace_back 사용 권장
std::vector&lt;ComplexType&gt; v;
v.emplace_back(arg1, arg2, arg3);  // 생성자 인자 직접 전달

// ✅ push_back도 괜찮음 (C++17 이후)
std::vector&lt;int&gt; numbers;
numbers.push_back(42);  // 간단한 타입은 가독성 우선

// ✅ 이미 생성된 객체는 push_back
Student s(&quot;Alice&quot;, 20);
v.push_back(std::move(s));  // 명시적 의도</code></pre>
<p><strong>결론</strong></p>
<ul>
<li><strong>복잡한 객체나 여러 인자</strong>: <code>emplace_back</code> 사용</li>
<li><strong>간단한 타입이나 가독성 우선</strong>: <code>push_back</code>도 OK</li>
<li><strong>현대 C++에서는 큰 차이 없는 경우 많음</strong></li>
<li><strong>일관성 있게 사용하는 것이 중요</strong></li>
</ul>
<h3 id="🔸-stdspan-c20">🔸 std::span (C++20)</h3>
<p><strong>std::span이란?</strong></p>
<ul>
<li>연속된 메모리 공간을 참조하는 경량 뷰</li>
<li>소유권 없이 데이터를 참조만 함</li>
<li>배열, vector, array 등을 통일된 인터페이스로 다룸</li>
</ul>
<p><strong>기본 사용법</strong></p>
<pre><code class="language-cpp">#include &lt;span&gt;

void print(std::span&lt;int&gt; data)  // 배열, vector, array 모두 받음
{
    for (int val : data)
    {
        std::cout &lt;&lt; val &lt;&lt; &quot; &quot;;
    }
    std::cout &lt;&lt; std::endl;
}

int main()
{
    // C 배열
    int arr[] = {1, 2, 3, 4, 5};
    print(arr);

    // std::array
    std::array&lt;int, 5&gt; stdArr{1, 2, 3, 4, 5};
    print(stdArr);

    // std::vector
    std::vector&lt;int&gt; vec{1, 2, 3, 4, 5};
    print(vec);

    return 0;
}</code></pre>
<p><strong>std::span의 구조</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class span
{
    T* ptr;      // 데이터 시작 주소 (8 bytes)
    size_t size; // 원소 개수 (8 bytes)
};

// sizeof(std::span&lt;int&gt;) == 16 bytes</code></pre>
<p><strong>메모리 레이아웃</strong></p>
<pre><code>Vector:
┌──────────┐
│ vec      │ (스택)
│ ptr ─────┼───────→ [1][2][3][4][5] (힙)
│ size: 5  │
│ cap: 5   │
└──────────┘

Span:
┌──────────┐
│ span     │ (스택)
│ ptr ─────┼───────→ [1][2][3][4][5] (vec의 데이터를 참조)
│ size: 5  │
└──────────┘
  ↑ 소유권 없음!</code></pre><p><strong>주의사항: Dangling Span</strong></p>
<pre><code class="language-cpp">std::span&lt;int&gt; createSpan()
{
    std::vector&lt;int&gt; vec{1, 2, 3, 4, 5};
    return std::span{vec};  // ❌ 위험!
}  // vec 소멸

int main()
{
    auto sp = createSpan();
    // sp는 이미 소멸된 vec의 메모리를 가리킴 (Dangling)
    std::cout &lt;&lt; sp[0] &lt;&lt; std::endl;  // ❌ Undefined Behavior

    return 0;
}</code></pre>
<p><strong>재할당 시 문제</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; vec{1, 2, 3};
std::span&lt;int&gt; sp{vec};

std::cout &lt;&lt; sp[0] &lt;&lt; std::endl;  // ✅ 1

vec.push_back(4);
vec.push_back(5);  // capacity 부족 → 재할당!

// sp는 여전히 옛날 메모리를 가리킴
std::cout &lt;&lt; sp[0] &lt;&lt; std::endl;  // ❌ Undefined Behavior</code></pre>
<p><strong>안전한 사용법</strong></p>
<pre><code class="language-cpp">void process(std::span&lt;int&gt; data)
{
    // data 사용
    for (int&amp; val : data)
    {
        val *= 2;
    }
}  // span 소멸, 하지만 원본 데이터는 유지

int main()
{
    std::vector&lt;int&gt; vec{1, 2, 3, 4, 5};

    // ✅ 안전: process 내부에서만 사용
    process(vec);

    // vec는 여전히 유효
    for (int val : vec)
    {
        std::cout &lt;&lt; val &lt;&lt; &quot; &quot;;  // 2 4 6 8 10
    }

    return 0;
}</code></pre>
<p><strong>std::span의 장점</strong></p>
<ol>
<li><p><strong>통일된 인터페이스</strong></p>
<ul>
<li>배열, vector, array를 동일하게 처리</li>
<li>함수 오버로딩 불필요</li>
</ul>
</li>
<li><p><strong>효율적</strong></p>
<ul>
<li>복사 비용 없음 (16 bytes만)</li>
<li>소유권 없이 참조만</li>
</ul>
</li>
<li><p><strong>부분 뷰 생성</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; vec{1, 2, 3, 4, 5};

std::span&lt;int&gt; all{vec};                    // 전체
std::span&lt;int&gt; first3 = all.first(3);       // 처음 3개
std::span&lt;int&gt; last2 = all.last(2);         // 마지막 2개
std::span&lt;int&gt; middle = all.subspan(1, 3);  // 중간 3개</code></pre>
</li>
</ol>
<p><strong>std::span vs const T&amp;</strong></p>
<pre><code class="language-cpp">// ❌ 배열은 받을 수 없음
void process(const std::vector&lt;int&gt;&amp; data) { }

int arr[] = {1, 2, 3};
// process(arr);  // 컴파일 에러

// ✅ 배열도 받을 수 있음
void process(std::span&lt;int&gt; data) { }
process(arr);  // OK</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>배열과 컨테이너 선택 가이드</strong></p>
<ul>
<li><strong>C 배열</strong>: 레거시 코드나 성능이 극도로 중요한 경우만</li>
<li><strong>C++ 동적 배열 (new/delete)</strong>: ❌ 사용하지 말 것 (std::vector 사용 권장)</li>
<li><strong>std::array</strong>: 컴파일 타임 고정 크기, 스택 할당 필요시</li>
<li><strong>std::vector</strong>: 대부분의 경우 이것을 사용 (권장)</li>
</ul>
<p><strong>다차원 배열 vs 벡터</strong></p>
<ul>
<li>배열: 연속 메모리 (캐시 효율 좋음)</li>
<li>벡터: 분산 메모리 (유연함)</li>
<li>Wrapper로 연속 메모리 유지 가능</li>
</ul>
<p><strong>캐시 최적화</strong></p>
<ul>
<li>Inner loop는 연속된 메모리 접근</li>
<li>Row-major 순서 (행 우선)</li>
<li>10배 이상 성능 차이 가능</li>
</ul>
<p><strong>std::deque</strong></p>
<ul>
<li>양쪽 끝 O(1) 삽입/삭제</li>
<li>재할당 시 복사 불필요</li>
<li>두 번의 포인터 역참조, 캐시 비효율</li>
<li>실무에서 거의 사용 안 함</li>
</ul>
<p><strong>push_back vs emplace_back</strong></p>
<ul>
<li>복잡한 객체: <code>emplace_back</code> 권장</li>
<li>간단한 타입: 둘 다 OK</li>
<li>C++17 이후 성능 차이 줄어듦</li>
<li>일관성 있게 사용</li>
</ul>
<p><strong>std::span (C++20)</strong></p>
<ul>
<li>연속 메모리 참조 뷰</li>
<li>소유권 없음 (16 bytes)</li>
<li>Dangling 주의</li>
<li>재할당 시 무효화</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/container/array">C++ std::array</a>
<a href="https://en.cppreference.com/w/cpp/container/deque">C++ std::deque</a>
<a href="https://en.cppreference.com/w/cpp/container/span">C++ std::span</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] Vector]]></title>
            <link>https://velog.io/@sehee-jj/C-Vector</link>
            <guid>https://velog.io/@sehee-jj/C-Vector</guid>
            <pubDate>Sat, 17 Jan 2026 03:49:03 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-vector">📚 C++ Vector</h2>
<h3 id="🔸-vector란">🔸 Vector란?</h3>
<p><strong>Vector의 특징</strong></p>
<ul>
<li>동적 크기 배열 (Dynamic Size Array)</li>
<li>시퀀스 컨테이너 (Sequence Container)</li>
<li>연속된 메모리 공간에 원소 저장</li>
<li>C++에서 가장 많이 사용되는 컨테이너</li>
</ul>
<p><strong>Vector 객체의 메모리 구조</strong></p>
<pre><code>Vector 객체 (스택):
┌──────────────────────┐
│ 포인터 (8 bytes)    │ → 실제 데이터 (힙)
├──────────────────────┤
│ size (8 bytes)     │   현재 원소 개수
├──────────────────────┤
│ capacity (8 bytes) │  할당된 공간 크기
└──────────────────────┘</code></pre><h3 id="🔸-reserve와-메모리-재할당">🔸 reserve와 메모리 재할당</h3>
<p><strong>메모리 재할당 문제</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; vec;
vec.push_back(1);  // capacity: 1
vec.push_back(2);  // capacity: 2 (메모리 재할당!)
vec.push_back(3);  // capacity: 4 (메모리 재할당!)
vec.push_back(4);  // capacity: 4 (재할당 없음)
vec.push_back(5);  // capacity: 8 (메모리 재할당!)</code></pre>
<p><strong>재할당 과정:</strong></p>
<pre><code>1. 새로운 더 큰 공간 할당 (보통 2배)
2. 기존 원소들을 새 공간으로 복사/이동 (O(n))
3. 기존 공간 해제</code></pre><p><strong>문제점:</strong></p>
<ul>
<li>벡터 뒤에 다른 메모리가 할당되어 있으면 확장 불가</li>
<li>새 공간 할당 후 전체 복사/이동 발생</li>
<li><strong>시간 복잡도: O(n)</strong></li>
<li>성능 저하 발생</li>
</ul>
<p><strong>reserve로 해결</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; vec;
vec.reserve(100);  // 미리 100개 공간 확보

for (int i = 0; i &lt; 100; i++)
{
    vec.push_back(i);  // 재할당 없음!
}</code></pre>
<p><strong>reserve의 효과:</strong></p>
<ul>
<li>메모리 재할당 횟수 감소</li>
<li>불필요한 복사/이동 제거</li>
<li>성능 향상</li>
</ul>
<h3 id="🔸-noexcept와-move-최적화">🔸 noexcept와 Move 최적화</h3>
<p><strong>문제 상황</strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    explicit Cat(std::string name) : mName{std::move(name)}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; Cat constructor&quot; &lt;&lt; std::endl;
    }

    ~Cat()
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; ~Cat()&quot; &lt;&lt; std::endl;
    }

    Cat(const Cat&amp; other) : mName{other.mName}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; copy constructor&quot; &lt;&lt; std::endl;
    }

    Cat(Cat&amp;&amp; other) : mName{std::move(other.mName)}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; move constructor&quot; &lt;&lt; std::endl;
    }

private:
    std::string mName;
};

int main()
{
    std::vector&lt;Cat&gt; cats;
    cats.emplace_back(&quot;kitty&quot;);
    cats.emplace_back(&quot;nabi&quot;);

    return 0;
}</code></pre>
<p><strong>출력:</strong></p>
<pre><code>kitty Cat constructor
nabi Cat constructor
kitty copy constructor  ← ❌ Move가 아닌 Copy!
kitty ~Cat()
kitty ~Cat()
nabi ~Cat()</code></pre><p><strong>왜 Copy가 발생했을까?</strong></p>
<p>메모리 재할당 과정:</p>
<pre><code>1. kitty 추가 (capacity: 1)
2. nabi 추가 시도
3. capacity 부족 → 새 공간 할당 (capacity: 2)
4. kitty를 새 공간으로 이동
   → 하지만 Copy Constructor 호출됨!</code></pre><p><strong>컴파일러가 Copy를 선택하는 이유:</strong></p>
<ul>
<li>Move Constructor에서 예외가 발생하면 원본 데이터가 손상될 수 있음</li>
<li>Copy는 예외가 발생해도 원본이 안전함 (Strong Exception Guarantee)</li>
<li>안전을 위해 Move 대신 Copy 선택</li>
</ul>
<p><strong>해결 방법 1: noexcept 추가</strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    explicit Cat(std::string name) : mName{std::move(name)}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; Cat constructor&quot; &lt;&lt; std::endl;
    }

    ~Cat() noexcept
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; ~Cat()&quot; &lt;&lt; std::endl;
    }

    Cat(const Cat&amp; other) : mName{other.mName}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; copy constructor&quot; &lt;&lt; std::endl;
    }

    Cat(Cat&amp;&amp; other) noexcept  // ✅ noexcept 추가
        : mName{std::move(other.mName)}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; move constructor&quot; &lt;&lt; std::endl;
    }

    Cat&amp; operator=(const Cat&amp; other)
    {
        mName = other.mName;
        return *this;
    }

    Cat&amp; operator=(Cat&amp;&amp; other) noexcept  // ✅ noexcept 추가
    {
        mName = std::move(other.mName);
        return *this;
    }

private:
    std::string mName;
};

int main()
{
    std::vector&lt;Cat&gt; cats;
    cats.emplace_back(&quot;kitty&quot;);
    cats.emplace_back(&quot;nabi&quot;);

    return 0;
}</code></pre>
<p><strong>출력 (noexcept 추가 후):</strong></p>
<pre><code>kitty Cat constructor
nabi Cat constructor
kitty move constructor  ← ✅ Move로 변경!
~Cat()                  ← 이동된 원본의 소멸자 (mName이 비어있음)
kitty ~Cat()            ← 새 위치의 kitty
nabi ~Cat()</code></pre><p><strong>해결 방법 2: reserve 사용</strong></p>
<pre><code class="language-cpp">int main()
{
    std::vector&lt;Cat&gt; cats;
    cats.reserve(2);  // ✅ 미리 공간 확보

    cats.emplace_back(&quot;kitty&quot;);
    cats.emplace_back(&quot;nabi&quot;);
    // 재할당 없음 → Copy/Move 모두 발생하지 않음!

    return 0;
}</code></pre>
<p><strong>noexcept를 붙여야 하는 함수</strong></p>
<ol>
<li><strong>Destructor</strong> (소멸자)</li>
<li><strong>Move Constructor</strong> (이동 생성자)</li>
<li><strong>Move Assignment</strong> (이동 대입 연산자)</li>
</ol>
<p><strong>이유:</strong></p>
<ul>
<li>위 함수들은 새로운 리소스를 요청하지 않음</li>
<li>메모리 할당 실패 등의 예외가 발생할 가능성이 없음</li>
<li><code>noexcept</code>를 명시하면 컴파일러가 최적화 가능</li>
</ul>
<h3 id="🔸-vector-for-loop의-함정">🔸 Vector for Loop의 함정</h3>
<p><strong>세 가지 반복문 방식</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{0, 1, 0, 1};

// 1. Index-based
for (std::size_t idx = 0; idx &lt; nums.size(); idx++)
{
    if (nums[idx] == 0)
    {
        nums.emplace_back(2);
    }
}

// 2. Iterator-based
for (auto itr = nums.begin(); itr != nums.end(); itr++)
{
    if (*itr == 0)
    {
        nums.emplace_back(2);
    }
}

// 3. Range-based
for (auto&amp; num : nums)
{
    if (num == 0)
    {
        nums.emplace_back(2);
    }
}</code></pre>
<p><strong>결과:</strong></p>
<ul>
<li><strong>Index-based</strong>: ✅ 정상 동작</li>
<li><strong>Iterator-based</strong>: ❌ 잘못된 결과 또는 무한 루프</li>
<li><strong>Range-based</strong>: ❌ 잘못된 결과 또는 크래시</li>
</ul>
<p><strong>Iterator-based의 문제</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{0, 1, 0, 1};  // capacity: 4

for (auto itr = nums.begin(); itr != nums.end(); itr++)
{
    if (*itr == 0)  // 첫 번째 0 발견
    {
        nums.emplace_back(2);  // capacity 부족 → 재할당!
        // ❌ 문제: 재할당으로 메모리 주소 변경
        //    itr은 여전히 옛날 주소를 가리킴 (dangling iterator)
        //    nums.end()는 새 주소를 가리킴
    }
}</code></pre>
<p><strong>Range-based의 문제</strong></p>
<p>Range-based for는 내부적으로 iterator를 사용하므로 같은 문제 발생</p>
<pre><code class="language-cpp">// C++17: Range-based는 이렇게 변환됨
{
    auto&amp;&amp; __range = nums;
    auto __begin = __range.begin();
    auto __end = __range.end();
    for (; __begin != __end; ++__begin)
    {
        auto&amp; num = *__begin;
        // capacity 변경 시 __begin과 __end가 invalidate됨
    }
}</code></pre>
<p><strong>Index-based는 왜 안전한가?</strong></p>
<pre><code class="language-cpp">for (std::size_t idx = 0; idx &lt; nums.size(); idx++)
{
    if (nums[idx] == 0)
    {
        nums.emplace_back(2);
        // nums.size()는 매번 새로 계산됨
        // nums[idx]는 매번 새 주소에서 접근
        // ✅ 재할당되어도 문제없음
    }
}</code></pre>
<p><strong>핵심 규칙</strong></p>
<p><strong>❗ For loop 내부에서 vector의 크기가 변경(증가)되면 반드시 index-based를 사용!</strong></p>
<pre><code class="language-cpp">// ✅ 안전: Index-based
for (std::size_t i = 0; i &lt; vec.size(); i++)
{
    vec.push_back(x);  // OK
}

// ❌ 위험: Iterator-based
for (auto it = vec.begin(); it != vec.end(); it++)
{
    vec.push_back(x);  // Undefined Behavior 가능!
}

// ❌ 위험: Range-based
for (auto&amp; elem : vec)
{
    vec.push_back(x);  // Undefined Behavior 가능!
}</code></pre>
<p><strong>예외: reserve로 미리 공간 확보</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{0, 1, 0, 1};
nums.reserve(100);  // 충분한 공간 확보

// ✅ capacity 변경이 없으므로 iterator도 안전
for (auto it = nums.begin(); it != nums.end(); it++)
{
    if (*it == 0)
    {
        nums.emplace_back(2);  // 재할당 없음 → 안전
    }
}</code></pre>
<h3 id="🔸-erase-vs-remove-erase-remove-idiom">🔸 erase vs remove (erase-remove idiom)</h3>
<p><strong>erase의 비효율성</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 짝수 제거 (비효율적인 방법)
for (auto it = nums.begin(); it != nums.end();)
{
    if (*it % 2 == 0)
    {
        it = nums.erase(it);  // ❌ 매번 뒤의 원소들을 이동
    }
    else
    {
        ++it;
    }
}</code></pre>
<p><strong>erase의 동작:</strong></p>
<pre><code>[1, 2, 3, 4, 5]에서 2 제거:

1. 2 제거
2. 뒤의 모든 원소를 앞으로 이동
   [1, _, 3, 4, 5]
   [1, 3, _, 4, 5]
   [1, 3, 4, _, 5]
   [1, 3, 4, 5, _]
3. size 감소

→ 한 번 제거할 때마다 O(n)
→ k개 제거 시 O(n*k)</code></pre><p><strong>remove의 효율적인 동작</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// std::remove는 제거할 원소를 뒤로 모음
auto new_end = std::remove_if(nums.begin(), nums.end(), 
    [](int n) { return n % 2 == 0; }
);

// 실제 제거는 erase로
nums.erase(new_end, nums.end());</code></pre>
<p><strong>remove의 two-pointer 알고리즘:</strong></p>
<pre><code>초기: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
       ↑
     write (복사할 위치)
       ↑
     read (확인할 위치)

1. read=1 (홀수) → write 위치에 복사, 둘 다 전진
   [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
       ↑
     write
       ↑
     read

2. read=2 (짝수) → 복사 안 함, read만 전진
   [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
       ↑
     write
          ↑
         read

3. read=3 (홀수) → write에 복사, 둘 다 전진
   [1, 3, 3, 4, 5, 6, 7, 8, 9, 10]
          ↑
        write
             ↑
           read

4. 반복...

최종: [1, 3, 5, 7, 9, 6, 7, 8, 9, 10]
                     ↑
                   new_end (반환값)

→ new_end부터 end()까지 erase
→ 총 O(n) 한 번만!</code></pre><p><strong>erase-remove idiom (표준 패턴)</strong></p>
<pre><code class="language-cpp">// ✅ 효율적인 방법
nums.erase(
    std::remove_if(nums.begin(), nums.end(), 
        [](int n) { return n % 2 == 0; }
    ),
    nums.end()
);

// 한 줄로 표현 가능</code></pre>
<p><strong>성능 비교:</strong></p>
<ul>
<li><strong>erase 반복</strong>: O(n*k) - k는 제거할 원소 개수</li>
<li><strong>erase-remove idiom</strong>: O(n) - 단 한 번의 순회</li>
</ul>
<p><strong>C++20: std::erase_if</strong></p>
<pre><code class="language-cpp">// C++20부터는 더 간단
std::erase_if(nums, [](int n) { return n % 2 == 0; });</code></pre>
<h3 id="🔸-vector-알고리즘">🔸 Vector 알고리즘</h3>
<p><strong>정렬 알고리즘</strong></p>
<p><strong>std::sort (일반 정렬)</strong></p>
<pre><code class="language-cpp">#include &lt;algorithm&gt;

std::vector&lt;int&gt; nums{5, 2, 8, 1, 9, 3};

std::sort(nums.begin(), nums.end());
// nums: {1, 2, 3, 5, 8, 9}

// 내림차순
std::sort(nums.begin(), nums.end(), std::greater&lt;int&gt;{});
// nums: {9, 8, 5, 3, 2, 1}

// 커스텀 비교
std::sort(nums.begin(), nums.end(), [](int a, int b) {
    return std::abs(a) &lt; std::abs(b);  // 절댓값 기준
});</code></pre>
<p><strong>시간 복잡도: O(n log n)</strong></p>
<p><strong>std::stable_sort (안정 정렬)</strong></p>
<pre><code class="language-cpp">struct Person
{
    std::string name;
    int age;
};

std::vector&lt;Person&gt; people{
    {&quot;Alice&quot;, 25},
    {&quot;Bob&quot;, 30},
    {&quot;Charlie&quot;, 25},
    {&quot;David&quot;, 30}
};

// 나이 기준 정렬
std::stable_sort(people.begin(), people.end(), 
    [](const Person&amp; a, const Person&amp; b) {
        return a.age &lt; b.age;
    }
);

// stable_sort: 나이가 같으면 원래 순서 유지
// Alice(25), Charlie(25), Bob(30), David(30)

// sort: 나이가 같으면 순서 보장 안 됨
// Charlie(25), Alice(25), David(30), Bob(30) 가능</code></pre>
<p><strong>stable_sort의 특징:</strong></p>
<ul>
<li>동일한 값의 원소들의 상대적 순서 유지</li>
<li><strong>시간 복잡도: O(n log n)</strong> (merge sort 기반으로 추측)</li>
<li>sort보다 약간 느리지만 안정성 보장</li>
</ul>
<p><strong>std::partial_sort (부분 정렬)</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{5, 2, 8, 1, 9, 3, 7, 4, 6};

// 처음 3개만 정렬
std::partial_sort(nums.begin(), nums.begin() + 3, nums.end());
// nums: {1, 2, 3, ?, ?, ?, ?, ?, ?}
//        ↑정렬됨  ↑정렬 안 됨 (순서 불확정)

// 실제: {1, 2, 3, 8, 9, 5, 7, 4, 6}</code></pre>
<p><strong>사용 사례:</strong></p>
<ul>
<li>Top K 문제 (상위 K개만 필요)</li>
<li>전체 정렬보다 빠름</li>
<li><strong>시간 복잡도: O(n log k)</strong> - k는 부분 정렬할 개수</li>
</ul>
<p><strong>std::nth_element</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{5, 2, 8, 1, 9, 3, 7, 4, 6};

// 중앙값 찾기 (5번째로 작은 원소)
std::nth_element(nums.begin(), nums.begin() + 4, nums.end());
// nums[4]는 정확히 5번째로 작은 원소
// nums[4] 왼쪽은 모두 작거나 같음
// nums[4] 오른쪽은 모두 크거나 같음
// 하지만 양쪽이 정렬된 것은 아님

std::cout &lt;&lt; nums[4] &lt;&lt; std::endl;  // 5 (중앙값)</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li>n번째 원소만 정확한 위치에 배치</li>
<li>양쪽은 정렬되지 않음</li>
<li><strong>시간 복잡도: O(n)</strong> (평균)</li>
<li>중앙값, 백분위수 찾기에 유용</li>
</ul>
<p><strong>최솟값/최댓값 찾기</strong></p>
<pre><code class="language-cpp">std::vector&lt;int&gt; nums{5, 2, 8, 1, 9, 3};

// 최솟값
auto min_it = std::min_element(nums.begin(), nums.end());
std::cout &lt;&lt; *min_it &lt;&lt; std::endl;  // 1

// 최댓값
auto max_it = std::max_element(nums.begin(), nums.end());
std::cout &lt;&lt; *max_it &lt;&lt; std::endl;  // 9

// 최솟값과 최댓값 동시에
auto [min_it2, max_it2] = std::minmax_element(nums.begin(), nums.end());
std::cout &lt;&lt; *min_it2 &lt;&lt; &quot;, &quot; &lt;&lt; *max_it2 &lt;&lt; std::endl;  // 1, 9

// 인덱스 찾기
auto min_idx = std::distance(nums.begin(), min_it);
std::cout &lt;&lt; &quot;Min index: &quot; &lt;&lt; min_idx &lt;&lt; std::endl;  // 3</code></pre>
<p><strong>std::distance</strong></p>
<ul>
<li>Iterator 간의 거리 계산</li>
<li>인덱스를 구할 때 유용<pre><code class="language-cpp">auto it = std::find(nums.begin(), nums.end(), 8);
if (it != nums.end())
{
  std::size_t index = std::distance(nums.begin(), it);
  std::cout &lt;&lt; &quot;Found at index: &quot; &lt;&lt; index &lt;&lt; std::endl;
}</code></pre>
</li>
</ul>
<p><strong>합계 및 누적 연산</strong></p>
<p><strong>std::accumulate</strong></p>
<pre><code class="language-cpp">#include &lt;numeric&gt;

std::vector&lt;int&gt; nums{1, 2, 3, 4, 5};

// 합계
int sum = std::accumulate(nums.begin(), nums.end(), 0);
std::cout &lt;&lt; sum &lt;&lt; std::endl;  // 15

// 곱셈
int product = std::accumulate(nums.begin(), nums.end(), 1,
    [](int acc, int n) { return acc * n; }
);
std::cout &lt;&lt; product &lt;&lt; std::endl;  // 120

// 문자열 연결
std::vector&lt;std::string&gt; words{&quot;Hello&quot;, &quot; &quot;, &quot;World&quot;};
std::string result = std::accumulate(words.begin(), words.end(), 
    std::string{},
    [](std::string acc, const std::string&amp; s) { return acc + s; }
);
std::cout &lt;&lt; result &lt;&lt; std::endl;  // &quot;Hello World&quot;</code></pre>
<p><strong>std::reduce (C++17)</strong></p>
<pre><code class="language-cpp">// std::accumulate와 비슷하지만 병렬 실행 가능
int sum = std::reduce(std::execution::par,  // 병렬 실행
    nums.begin(), nums.end(), 0
);</code></pre>
<p><strong>차이점:</strong></p>
<ul>
<li><code>accumulate</code>: 순차 실행, 순서 보장</li>
<li><code>reduce</code>: 병렬 실행 가능, 순서 보장 안 됨 (결합 법칙 필요)</li>
</ul>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>Vector 메모리 관리</strong></p>
<ul>
<li>Vector 객체: 24 bytes (포인터 + size + capacity)</li>
<li><code>reserve()</code>로 재할당 방지</li>
<li><code>noexcept</code>로 move 최적화</li>
</ul>
<p><strong>반복문 주의사항</strong></p>
<ul>
<li>Loop 내부에서 크기 변경 시 <strong>반드시 index-based</strong> 사용</li>
<li>Iterator와 Range-based는 재할당 시 dangling</li>
</ul>
<p><strong>효율적인 삭제</strong></p>
<ul>
<li><code>erase</code> 반복: O(n*k)</li>
<li><code>erase-remove idiom</code>: O(n)</li>
<li>C++20: <code>std::erase_if</code></li>
</ul>
<p><strong>알고리즘 선택</strong></p>
<ul>
<li>전체 정렬: <code>std::sort</code> O(n log n)</li>
<li>안정 정렬: <code>std::stable_sort</code> O(n log n)</li>
<li>부분 정렬: <code>std::partial_sort</code> O(n log k)</li>
<li>n번째 원소: <code>std::nth_element</code> O(n)</li>
<li>최솟값/최댓값: <code>std::min_element</code> / <code>std::max_element</code> O(n)</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/container/vector">C++ std::vector</a>
<a href="https://en.cppreference.com/w/cpp/algorithm">C++ Algorithms Library</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 함수형 프로그래밍 & 람다]]></title>
            <link>https://velog.io/@sehee-jj/C-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EB%9E%8C%EB%8B%A4</link>
            <guid>https://velog.io/@sehee-jj/C-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EB%9E%8C%EB%8B%A4</guid>
            <pubDate>Thu, 15 Jan 2026 02:39:32 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-함수형-프로그래밍과-람다">📚 C++ 함수형 프로그래밍과 람다</h2>
<h3 id="🔸-functional-programming-함수형-프로그래밍">🔸 Functional Programming (함수형 프로그래밍)</h3>
<p><strong>함수형 프로그래밍이란?</strong></p>
<ul>
<li>함수를 일급 객체(First-class citizen)로 취급</li>
<li>함수를 변수에 저장하고, 인자로 전달하고, 반환 가능</li>
<li>불변성(Immutability)과 순수 함수(Pure Function) 강조</li>
</ul>
<p><strong>JavaScript 예제</strong></p>
<pre><code class="language-javascript">function plus(a) {
    let localVar = a;
    return function(x) {
        return localVar + x;
    }
}

let plus3 = plus(3);
let plus5 = plus(5);

console.log(plus3(10));  // 13
console.log(plus5(10));  // 15</code></pre>
<p><strong>핵심 개념:</strong></p>
<ul>
<li><code>plus</code> 함수는 다른 함수를 반환</li>
<li>반환된 함수는 <code>localVar</code>를 &quot;기억&quot;함 (클로저)</li>
<li><code>plus3</code>과 <code>plus5</code>는 각각 다른 상태를 가진 함수</li>
</ul>
<h3 id="🔸-c에서-함수-객체로-구현">🔸 C++에서 함수 객체로 구현</h3>
<p><strong>Functor (함수 객체) 사용</strong></p>
<pre><code class="language-cpp">class Plus
{
public:
    explicit Plus(int a) : localVar{a} {}

    int operator()(int x) const  // () 연산자 오버로딩
    {
        return localVar + x;
    }

private:
    int localVar;  // 상태를 저장
};

int main()
{
    Plus plus3{3};  // localVar = 3인 함수 객체
    Plus plus5{5};  // localVar = 5인 함수 객체

    std::cout &lt;&lt; plus3(10) &lt;&lt; std::endl;  // 13
    std::cout &lt;&lt; plus5(10) &lt;&lt; std::endl;  // 15

    return 0;
}</code></pre>
<p><strong>동작 원리:</strong></p>
<ul>
<li><code>operator()</code>를 오버로딩하여 객체를 함수처럼 호출 가능</li>
<li>멤버 변수로 상태를 저장 (JavaScript의 클로저와 유사)</li>
<li><code>plus3(10)</code> → <code>plus3.operator()(10)</code> 호출</li>
</ul>
<p><strong>Functor의 장점:</strong></p>
<ul>
<li>상태를 가질 수 있음</li>
<li>인라인 최적화 가능</li>
<li>타입 안전성</li>
</ul>
<h3 id="🔸-lambda-expression-람다-표현식">🔸 Lambda Expression (람다 표현식)</h3>
<p><strong>람다 문법</strong></p>
<pre><code class="language-cpp">[capture](parameters) -&gt; return_type { body }</code></pre>
<p><strong>위 Functor를 람다로 변환</strong></p>
<pre><code class="language-cpp">int main()
{
    auto lambdaPlus3 = [localVar = 3](int x)
    {
        return localVar + x;
    };

    std::cout &lt;&lt; lambdaPlus3(10) &lt;&lt; std::endl;  // 13

    // 또는 직접 호출
    std::cout &lt;&lt; [](int x) { return 3 + x; }(10) &lt;&lt; std::endl;  // 13

    return 0;
}</code></pre>
<p><strong>람다 vs Functor: 어셈블리 코드 동일</strong></p>
<pre><code class="language-cpp">// Functor
Plus plus3{3};
plus3(10);

// Lambda
auto plus3 = [localVar = 3](int x) { return localVar + x; };
plus3(10);

// 둘 다 컴파일 후 동일한 어셈블리 코드 생성!
// 람다는 컴파일러가 자동으로 Functor 클래스를 생성</code></pre>
<p><strong>메모리 구조 비교</strong></p>
<p><strong>Functor의 메모리 구조:</strong></p>
<pre><code>Plus 객체 (sizeof: 4 bytes):
┌──────────────┐
│ localVar: 3 │  (4 bytes)
└──────────────┘
+ operator() 멤버 함수 코드 (코드 영역)</code></pre><p><strong>Lambda의 메모리 구조:</strong></p>
<pre><code>람다 객체 (sizeof: 4 bytes):
┌──────────────┐
│ localVar: 3 │  (4 bytes, 캡처된 값)
└──────────────┘
+ 컴파일러가 생성한 operator() 코드 (코드 영역)</code></pre><p><strong>람다의 장점:</strong></p>
<ul>
<li>간결한 문법</li>
<li>즉석에서 함수 정의 가능</li>
<li>코드 가독성 향상</li>
</ul>
<h3 id="🔸-capture-캡처">🔸 Capture (캡처)</h3>
<p><strong>캡처 방식</strong></p>
<p><strong>1. Capture by Value (값 복사)</strong></p>
<pre><code class="language-cpp">int main()
{
    int a = 10;
    int b = 20;

    // [=]: 모든 외부 변수를 값으로 캡처
    auto lambda1 = [=]() {
        std::cout &lt;&lt; a &lt;&lt; &quot;, &quot; &lt;&lt; b &lt;&lt; std::endl;  // 10, 20
    };

    // [a, b]: 특정 변수만 값으로 캡처
    auto lambda2 = [a, b]() {
        std::cout &lt;&lt; a + b &lt;&lt; std::endl;  // 30
    };

    // [a]: a만 값으로 캡처
    auto lambda3 = [a]() {
        std::cout &lt;&lt; a &lt;&lt; std::endl;  // 10
        // std::cout &lt;&lt; b;  // ❌ 컴파일 에러! b는 캡처하지 않음
    };

    a = 100;  // 외부 변수 변경
    lambda1();  // 여전히 10, 20 출력 (복사본이므로)

    return 0;
}</code></pre>
<p><strong>2. Capture by Reference (참조)</strong></p>
<pre><code class="language-cpp">int main()
{
    int a = 10;
    int b = 20;

    // [&amp;]: 모든 외부 변수를 참조로 캡처
    auto lambda1 = [&amp;]() {
        std::cout &lt;&lt; a &lt;&lt; &quot;, &quot; &lt;&lt; b &lt;&lt; std::endl;
        a = 100;  // ✅ 외부 변수 수정 가능
    };

    // [&amp;a, &amp;b]: 특정 변수만 참조로 캡처
    auto lambda2 = [&amp;a, &amp;b]() {
        a += b;  // 외부 a를 직접 수정
    };

    lambda1();  // 10, 20
    std::cout &lt;&lt; a &lt;&lt; std::endl;  // 100 (수정됨)

    lambda2();
    std::cout &lt;&lt; a &lt;&lt; std::endl;  // 120

    return 0;
}</code></pre>
<p><strong>3. 혼합 캡처</strong></p>
<pre><code class="language-cpp">int main()
{
    int a = 10;
    int b = 20;

    // a는 값으로, b는 참조로
    auto lambda = [a, &amp;b]() {
        // a = 100;  // ❌ 에러! a는 const (값 캡처)
        b = 100;     // ✅ OK (참조 캡처)
        std::cout &lt;&lt; a &lt;&lt; &quot;, &quot; &lt;&lt; b &lt;&lt; std::endl;
    };

    lambda();
    std::cout &lt;&lt; b &lt;&lt; std::endl;  // 100 (수정됨)

    return 0;
}</code></pre>
<p><strong>4. Init Capture (C++14)</strong></p>
<pre><code class="language-cpp">int main()
{
    // 새로운 변수를 람다 내부에서 생성
    auto lambda = [value = 42, str = std::string(&quot;hello&quot;)]() {
        std::cout &lt;&lt; value &lt;&lt; &quot;, &quot; &lt;&lt; str &lt;&lt; std::endl;
    };

    lambda();  // 42, hello

    // Move capture
    std::unique_ptr&lt;int&gt; ptr = std::make_unique&lt;int&gt;(100);
    auto lambda2 = [p = std::move(ptr)]() {
        std::cout &lt;&lt; *p &lt;&lt; std::endl;
    };
    // ptr은 이제 nullptr

    return 0;
}</code></pre>
<h3 id="🔸-capture-by-reference-주의사항">🔸 Capture by Reference 주의사항</h3>
<p><strong>Dangling Reference 문제</strong></p>
<pre><code class="language-cpp">std::function&lt;void()&gt; createLambda()
{
    int localVar = 10;

    // ❌ 위험! localVar은 함수가 끝나면 소멸
    return [&amp;localVar]() {
        std::cout &lt;&lt; localVar &lt;&lt; std::endl;  // Dangling reference!
    };
}

int main()
{
    auto lambda = createLambda();
    lambda();  // ❌ Undefined Behavior! localVar은 이미 소멸됨

    return 0;
}</code></pre>
<p><strong>해결 방법: Capture by Value</strong></p>
<pre><code class="language-cpp">std::function&lt;void()&gt; createLambda()
{
    int localVar = 10;

    // ✅ 안전: 값으로 캡처
    return [localVar]() {
        std::cout &lt;&lt; localVar &lt;&lt; std::endl;
    };
}

int main()
{
    auto lambda = createLambda();
    lambda();  // ✅ 10 출력

    return 0;
}</code></pre>
<p><strong>힙 메모리의 경우도 주의</strong></p>
<pre><code class="language-cpp">void dangerousCode()
{
    auto ptr = std::make_shared&lt;int&gt;(42);

    auto lambda = [&amp;ptr]() {  // ❌ 참조 캡처
        std::cout &lt;&lt; *ptr &lt;&lt; std::endl;
    };

    // ptr이 여기서 소멸되면 lambda는 dangling reference를 가짐
}</code></pre>
<p><strong>캡처 선택 가이드</strong></p>
<ul>
<li><strong>작은 객체</strong> (int, double 등): 값 캡처</li>
<li><strong>큰 객체</strong>: 참조 캡처 (수명 관리 주의!)</li>
<li><strong>수정이 필요한 경우</strong>: 참조 캡처</li>
<li><strong>람다를 반환하는 경우</strong>: 반드시 값 캡처</li>
<li><strong>스마트 포인터</strong>: move capture 고려</li>
</ul>
<h3 id="🔸-lambda-this-capture-클래스-내부">🔸 Lambda This Capture (클래스 내부)</h3>
<p><strong>클래스 멤버 접근</strong></p>
<pre><code class="language-cpp">class Counter
{
public:
    Counter() : count{0} {}

    void increment()
    {
        // [this]: 클래스의 this 포인터 캡처
        auto lambda = [this]() {
            count++;  // 멤버 변수 접근 가능
            std::cout &lt;&lt; &quot;Count: &quot; &lt;&lt; count &lt;&lt; std::endl;
        };

        lambda();
    }

    void incrementMultiple(int times)
    {
        // 멤버 함수도 호출 가능
        auto lambda = [this](int n) {
            for (int i = 0; i &lt; n; i++)
            {
                this-&gt;count++;  // 명시적으로 this-&gt; 사용 가능
            }
            display();  // 멤버 함수 호출
        };

        lambda(times);
    }

    void display() const
    {
        std::cout &lt;&lt; &quot;Final count: &quot; &lt;&lt; count &lt;&lt; std::endl;
    }

private:
    int count;
};

int main()
{
    Counter counter;
    counter.increment();          // Count: 1
    counter.incrementMultiple(5); // Final count: 6

    return 0;
}</code></pre>
<p><strong>C++17: [*this] (Copy Capture)</strong></p>
<pre><code class="language-cpp">class MyClass
{
public:
    // 람다를 반환하는 경우: [*this] 필수!
    std::function&lt;void()&gt; createCallback()
    {
        // ❌ [this]: 위험! 객체가 소멸되면 dangling pointer
        // return [this]() {
        //     std::cout &lt;&lt; data &lt;&lt; std::endl;
        // };

        // ✅ [*this]: 안전! 객체 전체를 복사 (C++17)
        return [*this]() {
            std::cout &lt;&lt; data &lt;&lt; std::endl;  // 복사본 사용
        };
    }

    // 람다가 함수 내부에서만 사용되는 경우: [this] OK
    void processData()
    {
        std::vector&lt;int&gt; numbers{1, 2, 3};

        // ✅ [this]: 안전! 람다가 함수 밖으로 나가지 않음
        std::for_each(numbers.begin(), numbers.end(), [this](int n) {
            data += n;
        });
    }

private:
    int data{42};
};

int main()
{
    std::function&lt;void()&gt; callback;

    {
        MyClass obj;
        callback = obj.createCallback();  // 객체 복사됨

    }  // obj 소멸

    callback();  // ✅ 안전! 복사본 사용

    return 0;
}</code></pre>
<h3 id="🔸-higher-order-functions-고차-함수">🔸 Higher-Order Functions (고차 함수)</h3>
<p><strong>고차 함수란?</strong></p>
<ul>
<li>함수를 인자로 받거나 함수를 반환하는 함수</li>
<li>C++ STL에서 많이 사용</li>
</ul>
<p><strong>std::for_each</strong></p>
<pre><code class="language-cpp">#include &lt;algorithm&gt;
#include &lt;vector&gt;

int main()
{
    std::vector&lt;int&gt; numbers{1, 2, 3, 4, 5};

    // 람다를 인자로 전달
    std::for_each(numbers.begin(), numbers.end(), [](int n) {
        std::cout &lt;&lt; n &lt;&lt; &quot; &quot;;
    });
    std::cout &lt;&lt; std::endl;  // 1 2 3 4 5

    // 각 원소를 제곱
    std::for_each(numbers.begin(), numbers.end(), [](int&amp; n) {
        n = n * n;
    });
    // numbers는 이제 {1, 4, 9, 16, 25}

    return 0;
}</code></pre>
<p><strong>std::remove_if + erase</strong></p>
<pre><code class="language-cpp">int main()
{
    std::vector&lt;int&gt; numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 짝수 제거
    numbers.erase(
        std::remove_if(numbers.begin(), numbers.end(), [](int n) {
            return n % 2 == 0;  // 짝수면 true
        }),
        numbers.end()
    );

    // numbers는 이제 {1, 3, 5, 7, 9}
    for (int n : numbers)
    {
        std::cout &lt;&lt; n &lt;&lt; &quot; &quot;;
    }
    std::cout &lt;&lt; std::endl;

    return 0;
}</code></pre>
<p><strong>std::sort</strong></p>
<pre><code class="language-cpp">int main()
{
    std::vector&lt;int&gt; numbers{5, 2, 8, 1, 9, 3};

    // 내림차순 정렬
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a &gt; b;  // a가 b보다 크면 a를 앞에
    });

    // numbers는 이제 {9, 8, 5, 3, 2, 1}

    // 절댓값 기준 오름차순
    std::vector&lt;int&gt; nums{-5, 2, -8, 1, -3};
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return std::abs(a) &lt; std::abs(b);
    });
    // nums는 이제 {1, 2, -3, -5, -8}

    return 0;
}</code></pre>
<p><strong>std::transform</strong></p>
<pre><code class="language-cpp">int main()
{
    std::vector&lt;int&gt; numbers{1, 2, 3, 4, 5};
    std::vector&lt;int&gt; squared(numbers.size());

    // 각 원소를 제곱하여 새 벡터에 저장
    std::transform(numbers.begin(), numbers.end(), squared.begin(), 
        [](int n) {
            return n * n;
        }
    );

    // squared는 {1, 4, 9, 16, 25}

    return 0;
}</code></pre>
<p><strong>std::accumulate (std::reduce)</strong></p>
<pre><code class="language-cpp">#include &lt;numeric&gt;

int main()
{
    std::vector&lt;int&gt; numbers{1, 2, 3, 4, 5};

    // 합계
    int sum = std::accumulate(numbers.begin(), numbers.end(), 0,
        [](int acc, int n) {
            return acc + n;
        }
    );
    std::cout &lt;&lt; &quot;Sum: &quot; &lt;&lt; sum &lt;&lt; std::endl;  // 15

    // 곱셈
    int product = std::accumulate(numbers.begin(), numbers.end(), 1,
        [](int acc, int n) {
            return acc * n;
        }
    );
    std::cout &lt;&lt; &quot;Product: &quot; &lt;&lt; product &lt;&lt; std::endl;  // 120

    return 0;
}</code></pre>
<p><strong>std::count_if</strong></p>
<pre><code class="language-cpp">int main()
{
    std::vector&lt;int&gt; numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 짝수 개수 세기
    int evenCount = std::count_if(numbers.begin(), numbers.end(), 
        [](int n) {
            return n % 2 == 0;
        }
    );

    std::cout &lt;&lt; &quot;Even count: &quot; &lt;&lt; evenCount &lt;&lt; std::endl;  // 5

    return 0;
}</code></pre>
<h3 id="🔸-stdfunction-함수-래퍼">🔸 std::function (함수 래퍼)</h3>
<p><strong>std::function이란?</strong></p>
<ul>
<li>호출 가능한 모든 객체를 저장할 수 있는 범용 함수 래퍼</li>
<li>일반 함수, 람다, 함수 객체, 멤버 함수 포인터 등 저장 가능</li>
</ul>
<p><strong>기본 사용법</strong></p>
<pre><code class="language-cpp">#include &lt;functional&gt;

int add(int a, int b) { return a + b; }

int main()
{
    // 함수 포인터 저장
    std::function&lt;int(int, int)&gt; func1 = add;
    std::cout &lt;&lt; func1(1, 2) &lt;&lt; std::endl;  // 3

    // 람다 저장
    std::function&lt;int(int, int)&gt; func2 = [](int a, int b) {
        return a * b;
    };
    std::cout &lt;&lt; func2(3, 4) &lt;&lt; std::endl;  // 12

    // 함수 객체 저장
    std::function&lt;int(int, int)&gt; func3 = std::plus&lt;int&gt;{};
    std::cout &lt;&lt; func3(5, 6) &lt;&lt; std::endl;  // 11

    return 0;
}</code></pre>
<p><strong>함수를 매개변수로 전달</strong></p>
<pre><code class="language-cpp">void execute(const std::function&lt;void(int)&gt;&amp; fn, int value)
{
    std::cout &lt;&lt; &quot;Executing function with &quot; &lt;&lt; value &lt;&lt; std::endl;
    fn(value);
}

int main()
{
    // 람다 전달
    execute([](int x) {
        std::cout &lt;&lt; &quot;Result: &quot; &lt;&lt; x * 2 &lt;&lt; std::endl;
    }, 10);

    // 출력:
    // Executing function with 10
    // Result: 20

    return 0;
}</code></pre>
<p><strong>함수 컬렉션</strong></p>
<pre><code class="language-cpp">int main()
{
    std::vector&lt;std::function&lt;void(int)&gt;&gt; operations;

    // 여러 함수를 벡터에 저장
    operations.push_back([](int x) { 
        std::cout &lt;&lt; &quot;Double: &quot; &lt;&lt; x * 2 &lt;&lt; std::endl; 
    });
    operations.push_back([](int x) { 
        std::cout &lt;&lt; &quot;Square: &quot; &lt;&lt; x * x &lt;&lt; std::endl; 
    });
    operations.push_back([](int x) { 
        std::cout &lt;&lt; &quot;Cube: &quot; &lt;&lt; x * x * x &lt;&lt; std::endl; 
    });

    // 모든 함수 실행
    for (const auto&amp; op : operations)
    {
        op(5);
    }

    // 출력:
    // Double: 10
    // Square: 25
    // Cube: 125

    return 0;
}</code></pre>
<p><strong>콜백 시스템 구현</strong></p>
<pre><code class="language-cpp">class EventSystem
{
public:
    using Callback = std::function&lt;void(const std::string&amp;)&gt;;

    void registerCallback(Callback cb)
    {
        callbacks.push_back(cb);
    }

    void trigger(const std::string&amp; event)
    {
        std::cout &lt;&lt; &quot;Event triggered: &quot; &lt;&lt; event &lt;&lt; std::endl;
        for (const auto&amp; cb : callbacks)
        {
            cb(event);
        }
    }

private:
    std::vector&lt;Callback&gt; callbacks;
};

int main()
{
    EventSystem system;

    // 여러 콜백 등록
    system.registerCallback([](const std::string&amp; event) {
        std::cout &lt;&lt; &quot;  Logger: &quot; &lt;&lt; event &lt;&lt; std::endl;
    });

    system.registerCallback([](const std::string&amp; event) {
        std::cout &lt;&lt; &quot;  Handler: Processing &quot; &lt;&lt; event &lt;&lt; std::endl;
    });

    system.trigger(&quot;UserLogin&quot;);

    // 출력:
    // Event triggered: UserLogin
    //   Logger: UserLogin
    //   Handler: Processing UserLogin

    return 0;
}</code></pre>
<p><strong>std::function vs auto</strong></p>
<pre><code class="language-cpp">// auto: 컴파일 타임에 타입 결정, 더 빠름
auto lambda1 = [](int x) { return x * 2; };

// std::function: 런타임 다형성, 오버헤드 있음
std::function&lt;int(int)&gt; lambda2 = [](int x) { return x * 2; };

// auto는 타입이 다르면 저장 불가
std::vector&lt;auto&gt; funcs;  // ❌ 컴파일 에러!

// std::function은 시그니처가 같으면 저장 가능
std::vector&lt;std::function&lt;int(int)&gt;&gt; funcs;  // ✅ OK</code></pre>
<h3 id="🔸-함수형-프로그래밍의-장점">🔸 함수형 프로그래밍의 장점</h3>
<p><strong>Side Effect가 없음</strong></p>
<pre><code class="language-cpp">// ❌ Side Effect 있음 (OOP 스타일)
class Counter
{
    int count = 0;
public:
    void increment() { count++; }  // 상태 변경
    int getCount() const { return count; }
};

// ✅ Side Effect 없음 (Functional 스타일)
int increment(int count) { return count + 1; }  // 새 값 반환, 원본 불변

int main()
{
    int count = 0;
    count = increment(count);  // 명시적 재할당
    count = increment(count);
    std::cout &lt;&lt; count &lt;&lt; std::endl;  // 2

    return 0;
}</code></pre>
<p><strong>순수 함수 (Pure Function)</strong></p>
<ul>
<li>같은 입력에 항상 같은 출력</li>
<li>외부 상태를 변경하지 않음</li>
<li>예측 가능하고 테스트하기 쉬움</li>
</ul>
<pre><code class="language-cpp">// ✅ 순수 함수
int add(int a, int b)
{
    return a + b;  // 항상 같은 결과
}

// ❌ 순수하지 않은 함수
int globalCounter = 0;
int incrementGlobal()
{
    return ++globalCounter;  // 외부 상태 변경
}</code></pre>
<p><strong>불변성 (Immutability)</strong></p>
<pre><code class="language-cpp">// Functional 스타일: 원본을 변경하지 않고 새로운 값 생성
std::vector&lt;int&gt; doubleValues(const std::vector&lt;int&gt;&amp; numbers)
{
    std::vector&lt;int&gt; result;
    std::transform(numbers.begin(), numbers.end(), 
        std::back_inserter(result),
        [](int n) { return n * 2; }
    );
    return result;  // 새 벡터 반환
}

int main()
{
    std::vector&lt;int&gt; original{1, 2, 3, 4, 5};
    std::vector&lt;int&gt; doubled = doubleValues(original);

    // original은 그대로 {1, 2, 3, 4, 5}
    // doubled는 {2, 4, 6, 8, 10}

    return 0;
}</code></pre>
<h3 id="🔸-oop-vs-functional-programming">🔸 OOP vs Functional Programming</h3>
<p><strong>OOP가 적합한 경우</strong></p>
<ul>
<li>상태를 관리해야 하는 경우 (게임 캐릭터, GUI 컴포넌트)</li>
<li>객체 간의 복잡한 관계 (상속, 다형성)</li>
<li>대규모 시스템 설계</li>
</ul>
<pre><code class="language-cpp">// OOP 예제: 게임 캐릭터
class Character
{
    int health;
    int level;
    std::string name;

public:
    void takeDamage(int damage) { health -= damage; }
    void levelUp() { level++; health = 100; }
    bool isAlive() const { return health &gt; 0; }
};</code></pre>
<p><strong>Functional Programming이 적합한 경우</strong></p>
<ul>
<li>데이터 변환 파이프라인</li>
<li>병렬 처리 (Side Effect 없어서 안전)</li>
<li>수학적 계산</li>
</ul>
<pre><code class="language-cpp">// Functional 예제: 데이터 처리
auto result = numbers
    | std::views::filter([](int n) { return n % 2 == 0; })
    | std::views::transform([](int n) { return n * n; })
    | std::views::take(5);</code></pre>
<p><strong>두 패러다임 함께 사용</strong></p>
<pre><code class="language-cpp">class DataProcessor  // OOP: 클래스 구조
{
public:
    std::vector&lt;int&gt; process(const std::vector&lt;int&gt;&amp; data)
    {
        // Functional: 순수 함수 체인
        std::vector&lt;int&gt; result;
        std::copy_if(data.begin(), data.end(), 
            std::back_inserter(result),
            [](int n) { return n &gt; 0; }  // 양수만 필터
        );

        std::transform(result.begin(), result.end(), result.begin(),
            [](int n) { return n * 2; }  // 2배
        );

        return result;
    }
};</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>람다 표현식</strong></p>
<ul>
<li>함수 객체의 간결한 표현</li>
<li>컴파일러가 자동으로 클래스 생성</li>
<li>Functor와 동일한 성능</li>
</ul>
<p><strong>캡처 방식</strong></p>
<ul>
<li><strong>값 캡처</strong>: 작은 객체, 람다를 반환할 때</li>
<li><strong>참조 캡처</strong>: 큰 객체, 수정이 필요할 때 (수명 주의!)</li>
<li><strong>this 캡처</strong>: 멤버 접근 필요할 때</li>
<li><strong>[*this]</strong>: 객체 복사 (C++17, 안전)</li>
</ul>
<p><strong>STL 알고리즘</strong></p>
<ul>
<li><code>std::for_each</code>, <code>std::transform</code>, <code>std::remove_if</code></li>
<li><code>std::sort</code>, <code>std::accumulate</code>, <code>std::count_if</code></li>
<li>람다와 함께 사용하여 간결한 코드 작성</li>
</ul>
<p><strong>std::function</strong></p>
<ul>
<li>호출 가능한 모든 객체를 저장</li>
<li>함수를 인자로 전달하거나 컬렉션에 저장</li>
<li>오버헤드 있음 (auto가 더 빠름)</li>
</ul>
<p><strong>함수형 프로그래밍 장점</strong></p>
<ul>
<li>Side Effect 없음</li>
<li>순수 함수로 예측 가능</li>
<li>병렬 처리에 유리</li>
<li>테스트하기 쉬움</li>
</ul>
<p><strong>패러다임 선택</strong></p>
<ul>
<li>OOP: 상태 관리, 복잡한 시스템</li>
<li>Functional: 데이터 변환, 병렬 처리</li>
<li>실전에서는 두 패러다임을 적절히 혼합</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/lambda">C++ Lambda Expressions</a>
<a href="https://en.cppreference.com/w/cpp/utility/functional/function">C++ std::function</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 템플릿 (Templates)]]></title>
            <link>https://velog.io/@sehee-jj/C-%ED%85%9C%ED%94%8C%EB%A6%BF-Templates</link>
            <guid>https://velog.io/@sehee-jj/C-%ED%85%9C%ED%94%8C%EB%A6%BF-Templates</guid>
            <pubDate>Tue, 13 Jan 2026 07:24:06 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-템플릿-templates">📚 C++ 템플릿 (Templates)</h2>
<h3 id="🔸-템플릿이란">🔸 템플릿이란?</h3>
<p><strong>템플릿의 개념</strong></p>
<ul>
<li>변수의 타입을 미리 정하지 않고, <strong>컴파일 타임에 타입을 지정</strong>하는 기능</li>
<li>코드 재사용성을 높이고 타입 안전성을 보장</li>
<li>제네릭 프로그래밍(Generic Programming)의 핵심</li>
</ul>
<p><strong>템플릿의 동작 원리</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
T add(T a, T b)
{
    return a + b;
}

int main()
{
    int x = add(1, 2);        // int 버전 생성
    double y = add(1.5, 2.5); // double 버전 생성

    return 0;
}</code></pre>
<ul>
<li>템플릿 코드는 <strong>컴파일 전까지는 실제 코드로 존재하지 않음</strong></li>
<li>템플릿이 사용될 때 해당 타입에 맞는 함수가 <strong>자동 생성됨</strong> (Instantiation)</li>
</ul>
<h3 id="🔸-function-template-함수-템플릿">🔸 Function Template (함수 템플릿)</h3>
<p><strong>Function Overloading vs Template</strong></p>
<p><strong>❌ Function Overloading (비효율적)</strong></p>
<pre><code class="language-cpp">int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
float add(float a, float b) { return a + b; }
long add(long a, long b) { return a + b; }
// 타입마다 함수를 일일이 작성해야 함!</code></pre>
<p><strong>✅ Function Template (효율적)</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
T add(T a, T b)
{
    return a + b;
}

// 컴파일러가 필요한 버전을 자동 생성
add(1, 2);        // int 버전
add(1.5, 2.5);    // double 버전
add(1.0f, 2.0f);  // float 버전</code></pre>
<p><strong>템플릿 인스턴스화 (Template Instantiation)</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void print(T value)
{
    std::cout &lt;&lt; value &lt;&lt; std::endl;
}

int main()
{
    print(10);      // ✅ print&lt;int&gt;(int) 생성됨
    print(3.14);    // ✅ print&lt;double&gt;(double) 생성됨
    print(&quot;hello&quot;); // ✅ print&lt;const char*&gt;(const char*) 생성됨

    return 0;
}</code></pre>
<p><strong>명시적 템플릿 인자 지정</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
T getValue()
{
    return T{};
}

int main()
{
    auto x = getValue&lt;int&gt;();     // int 반환
    auto y = getValue&lt;double&gt;();  // double 반환

    return 0;
}</code></pre>
<h3 id="🔸-template-type-deduction-템플릿-타입-추론">🔸 Template Type Deduction (템플릿 타입 추론)</h3>
<p><strong>자동 타입 추론</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void print(T value)
{
    std::cout &lt;&lt; value &lt;&lt; std::endl;
}

int main()
{
    int x = 10;
    print(x);  // T는 int로 추론됨

    print(3.14);  // T는 double로 추론됨

    return 0;
}</code></pre>
<p><strong>auto 키워드와의 관계</strong></p>
<pre><code class="language-cpp">// auto와 template type deduction은 동일한 규칙 사용
template&lt;typename T&gt;
void func(T param) { }

auto x = 42;  // auto는 int로 추론
func(42);     // T는 int로 추론

// 둘 다 같은 타입 추론 규칙을 따름</code></pre>
<p><strong>타입 추론 규칙</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void func1(T param);        // 값으로 받음

template&lt;typename T&gt;
void func2(T&amp; param);       // L-value reference

template&lt;typename T&gt;
void func3(T&amp;&amp; param);      // Universal reference (forwarding reference)

int x = 10;
const int cx = x;
const int&amp; rx = x;

// func1: 값으로 받음 (const, reference 제거)
func1(x);   // T = int
func1(cx);  // T = int (const 제거)
func1(rx);  // T = int (const&amp;에서 const와 &amp; 제거)

// func2: L-value reference
func2(x);   // T = int, param은 int&amp;
func2(cx);  // T = const int, param은 const int&amp;
func2(rx);  // T = const int, param은 const int&amp;

// func3: Universal reference (나중에 설명)</code></pre>
<h3 id="🔸-universal-reference-forwarding-reference">🔸 Universal Reference (Forwarding Reference)</h3>
<p><strong>Universal Reference란?</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void printVar(T&amp;&amp; a)  // ✅ Universal reference (T&amp;&amp;)
{
    std::cout &lt;&lt; a &lt;&lt; std::endl;
}</code></pre>
<ul>
<li><code>T&amp;&amp;</code>는 <strong>R-value reference가 아니라</strong> Universal Reference</li>
<li>L-value와 R-value를 모두 받을 수 있음</li>
<li><strong>Template에서만</strong> 이런 특성을 가짐 (일반 함수의 <code>&amp;&amp;</code>는 R-value reference)</li>
</ul>
<p><strong>동작 원리: Reference Collapsing</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void printVar(T&amp;&amp; a)
{
    std::cout &lt;&lt; a &lt;&lt; std::endl;
}

int main()
{
    int x = 10;

    printVar(x);              // L-value 전달
    // T = int&amp;
    // T&amp;&amp; = int&amp; &amp;&amp; → int&amp; (reference collapsing)
    // 결과: printVar(int&amp; a)

    printVar(std::move(x));   // R-value 전달
    // T = int
    // T&amp;&amp; = int&amp;&amp;
    // 결과: printVar(int&amp;&amp; a)

    return 0;
}</code></pre>
<p><strong>Reference Collapsing 규칙</strong></p>
<pre><code class="language-cpp">T&amp;  &amp;  → T&amp;   // L-value reference
T&amp;  &amp;&amp; → T&amp;   // L-value reference
T&amp;&amp; &amp;  → T&amp;   // L-value reference
T&amp;&amp; &amp;&amp; → T&amp;&amp;  // R-value reference

// 핵심: &amp; 하나라도 있으면 &amp;, 둘 다 &amp;&amp;일 때만 &amp;&amp;</code></pre>
<h3 id="🔸-stdforward-vs-stdmove">🔸 std::forward vs std::move</h3>
<p><strong>Perfect Forwarding 문제</strong></p>
<pre><code class="language-cpp">void process(int&amp; x)  { std::cout &lt;&lt; &quot;L-value&quot; &lt;&lt; std::endl; }
void process(int&amp;&amp; x) { std::cout &lt;&lt; &quot;R-value&quot; &lt;&lt; std::endl; }

template&lt;typename T&gt;
void wrapper(T&amp;&amp; arg)
{
    // arg는 이름이 있는 변수이므로 L-value!
    process(arg);  // 항상 L-value로 전달됨
}

int main()
{
    int x = 10;
    wrapper(x);              // L-value 전달했지만
    wrapper(std::move(x));   // R-value 전달했지만
    // 둘 다 process(int&amp;)가 호출됨! ❌

    return 0;
}</code></pre>
<p><strong>std::move 사용 (잘못된 방법)</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void wrapper(T&amp;&amp; arg)
{
    process(std::move(arg));  // ❌ 항상 R-value로 전달
}

int main()
{
    int x = 10;
    wrapper(x);              // L-value인데 R-value로 전달됨! ❌
    wrapper(std::move(x));   // R-value로 전달 ✅

    return 0;
}</code></pre>
<p><strong>std::forward 사용 (올바른 방법)</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void wrapper(T&amp;&amp; arg)
{
    process(std::forward&lt;T&gt;(arg));  // ✅ 원래 타입 그대로 전달
}

int main()
{
    int x = 10;
    wrapper(x);              // L-value로 전달 ✅ process(int&amp;) 호출
    wrapper(std::move(x));   // R-value로 전달 ✅ process(int&amp;&amp;) 호출

    return 0;
}</code></pre>
<p><strong>std::move vs std::forward 비교</strong></p>
<pre><code class="language-cpp">// std::move: 무조건 R-value로 캐스팅
template&lt;typename T&gt;
void func1(T&amp;&amp; a)
{
    std::string localVar{std::move(a)};  // 항상 move
}

// std::forward: 원래 타입 유지
template&lt;typename T&gt;
void func2(T&amp;&amp; a)
{
    std::string localVar{std::forward&lt;T&gt;(a)};  // L-value면 copy, R-value면 move
}

int main()
{
    std::string s = &quot;hello&quot;;

    func1(s);              // move (원본 s가 비워질 수 있음)
    func1(std::move(s));   // move

    func2(s);              // copy (원본 s 유지)
    func2(std::move(s));   // move

    return 0;
}</code></pre>
<p><strong>핵심 정리</strong></p>
<ul>
<li><strong>std::move</strong>: 항상 R-value로 변환 (이동 강제)</li>
<li><strong>std::forward</strong>: 원래 값 카테고리 유지 (Perfect Forwarding)</li>
<li><strong>Universal Reference + std::forward</strong>: 가장 효율적인 패턴</li>
</ul>
<h3 id="🔸-template-instantiation-템플릿-인스턴스화">🔸 Template Instantiation (템플릿 인스턴스화)</h3>
<p><strong>Multiple Type Parameters (여러 타입 매개변수)</strong></p>
<pre><code class="language-cpp">template&lt;typename T, typename U&gt;
auto add(T a, U b)  // 서로 다른 타입도 가능
{
    return a + b;
}

int main()
{
    auto result1 = add(1, 2.5);      // T=int, U=double, 반환=double
    auto result2 = add(1.5f, 2);     // T=float, U=int, 반환=float
    auto result3 = add(1L, 2.5);     // T=long, U=double, 반환=double

    return 0;
}</code></pre>
<p><strong>명시적 반환 타입 지정</strong></p>
<pre><code class="language-cpp">template&lt;typename R, typename T, typename U&gt;
R add(T a, U b)
{
    return static_cast&lt;R&gt;(a + b);
}

int main()
{
    auto x = add&lt;double&gt;(1, 2);  // 명시적으로 double 반환
    auto y = add&lt;int&gt;(1.5, 2.5); // 명시적으로 int 반환 (4)

    return 0;
}</code></pre>
<p><strong>Non-Type Template Parameters (비타입 템플릿 매개변수)</strong></p>
<pre><code class="language-cpp">template&lt;typename T, size_t N&gt;  // N은 컴파일 타임 상수
class Array
{
public:
    T&amp; operator[](size_t index) { return data[index]; }
    size_t size() const { return N; }

private:
    T data[N];
};

int main()
{
    Array&lt;int, 5&gt; arr1;     // int 배열, 크기 5
    Array&lt;double, 10&gt; arr2; // double 배열, 크기 10

    std::cout &lt;&lt; arr1.size() &lt;&lt; std::endl;  // 5
    std::cout &lt;&lt; arr2.size() &lt;&lt; std::endl;  // 10

    return 0;
}</code></pre>
<p><strong>비타입 매개변수 제약</strong></p>
<ul>
<li>정수 타입 (<code>int</code>, <code>size_t</code>, <code>long</code> 등)</li>
<li>포인터</li>
<li>참조</li>
<li>enum</li>
<li>C++20부터: 부동소수점, 클래스 타입도 가능</li>
</ul>
<p><strong>Variadic Templates (가변 인자 템플릿)</strong></p>
<pre><code class="language-cpp">// Base case (재귀 종료)
void print()
{
    std::cout &lt;&lt; std::endl;
}

// Recursive case
template&lt;typename T, typename... Args&gt;  // Args는 parameter pack
void print(T first, Args... rest)
{
    std::cout &lt;&lt; first &lt;&lt; &quot; &quot;;
    print(rest...);  // 재귀 호출
}

int main()
{
    print(1, 2, 3, 4, 5);           // 1 2 3 4 5
    print(&quot;Hello&quot;, 42, 3.14, &#39;A&#39;);  // Hello 42 3.14 A

    return 0;
}</code></pre>
<p><strong>C++17: Fold Expression</strong></p>
<pre><code class="language-cpp">template&lt;typename... Args&gt;
auto sum(Args... args)
{
    return (args + ...);  // Fold expression
}

int main()
{
    std::cout &lt;&lt; sum(1, 2, 3, 4, 5) &lt;&lt; std::endl;        // 15
    std::cout &lt;&lt; sum(1.5, 2.5, 3.5) &lt;&lt; std::endl;        // 7.5

    return 0;
}</code></pre>
<h3 id="🔸-template-빌드-instantiation">🔸 Template 빌드 (Instantiation)</h3>
<p><strong>템플릿은 헤더 파일에 구현해야 함</strong></p>
<p><strong>❌ 잘못된 방법 (.h와 .cpp 분리)</strong></p>
<pre><code class="language-cpp">// Math.h
template&lt;typename T&gt;
T add(T a, T b);

// Math.cpp
template&lt;typename T&gt;
T add(T a, T b)
{
    return a + b;
}

// main.cpp
#include &quot;Math.h&quot;
int main()
{
    add(1, 2);  // ❌ 링크 에러!
    // Math.cpp에는 add&lt;int&gt;가 생성되지 않음
    return 0;
}</code></pre>
<p><strong>✅ 올바른 방법 (헤더에 구현)</strong></p>
<pre><code class="language-cpp">// Math.h
template&lt;typename T&gt;
T add(T a, T b)  // 헤더에 구현
{
    return a + b;
}

// main.cpp
#include &quot;Math.h&quot;
int main()
{
    add(1, 2);  // ✅ OK
    return 0;
}</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>템플릿은 사용 시점에 인스턴스화됨</li>
<li>컴파일러가 템플릿 정의를 볼 수 있어야 인스턴스화 가능</li>
<li><code>.cpp</code> 파일에 숨기면 다른 파일에서 정의를 볼 수 없음</li>
</ul>
<p><strong>대안: Explicit Instantiation (명시적 인스턴스화)</strong></p>
<pre><code class="language-cpp">// Math.h
template&lt;typename T&gt;
T add(T a, T b);

// Math.cpp
template&lt;typename T&gt;
T add(T a, T b)
{
    return a + b;
}

// 사용할 타입을 미리 명시
template int add&lt;int&gt;(int, int);
template double add&lt;double&gt;(double, double);

// main.cpp
#include &quot;Math.h&quot;
int main()
{
    add(1, 2);      // ✅ OK
    add(1.5, 2.5);  // ✅ OK
    // add(1L, 2L); // ❌ 에러! long 버전은 명시하지 않음
    return 0;
}</code></pre>
<h3 id="🔸-class-template-클래스-템플릿">🔸 Class Template (클래스 템플릿)</h3>
<p><strong>기본 사용법</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class Stack
{
public:
    void push(T item)
    {
        data.emplace_back(std::move(item));
    }

    bool pop(T&amp; item)
    {
        if (data.empty()) return false;
        item = std::move(data.back());
        data.pop_back();
        return true;
    }
    bool empty() const { return data.empty(); }

private:
    std::vector&lt;T&gt; data;
};

int main()
{
    Stack&lt;int&gt; intStack;
    intStack.push(1);
    intStack.push(2);

    Stack&lt;std::string&gt; strStack;
    strStack.push(&quot;hello&quot;);
    strStack.push(&quot;world&quot;);

    return 0;
}</code></pre>
<p><strong>C++17: Class Template Argument Deduction (CTAD)</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
class Pair
{
public:
    Pair(T a, T b) : first{a}, second{b} {}

    T first;
    T second;
};

int main()
{
    // C++17 이전
    Pair&lt;int&gt; p1{1, 2};

    // C++17 이후: 타입 추론
    Pair p2{1, 2};        // Pair&lt;int&gt;로 추론
    Pair p3{1.5, 2.5};    // Pair&lt;double&gt;로 추론

    return 0;
}</code></pre>
<h3 id="🔸-template-alias-템플릿-별칭">🔸 Template Alias (템플릿 별칭)</h3>
<p><strong>using을 이용한 템플릿 별칭</strong></p>
<pre><code class="language-cpp">// 복잡한 타입
std::map&lt;std::string, std::vector&lt;std::pair&lt;int, double&gt;&gt;&gt; data;

// Alias로 단순화
template&lt;typename K, typename V&gt;
using DataMap = std::map&lt;K, std::vector&lt;std::pair&lt;int, V&gt;&gt;&gt;;

DataMap&lt;std::string, double&gt; data;  // 훨씬 읽기 쉬움!</code></pre>
<p><strong>부분 특수화와 함께 사용</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
using Ptr = std::unique_ptr&lt;T&gt;;

int main()
{
    Ptr&lt;int&gt; intPtr = std::make_unique&lt;int&gt;(42);
    Ptr&lt;std::string&gt; strPtr = std::make_unique&lt;std::string&gt;(&quot;hello&quot;);

    return 0;
}</code></pre>
<h3 id="🔸-variable-template-변수-템플릿">🔸 Variable Template (변수 템플릿)</h3>
<p><strong>C++14: Variable Template</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
constexpr T pi = T{3.1415926535897932385};

int main()
{
    std::cout &lt;&lt; pi&lt;float&gt; &lt;&lt; std::endl;       // float 정밀도
    std::cout &lt;&lt; pi&lt;double&gt; &lt;&lt; std::endl;      // double 정밀도
    std::cout &lt;&lt; pi&lt;long double&gt; &lt;&lt; std::endl; // long double 정밀도

    return 0;
}</code></pre>
<p><strong>타입 특성과 함께 사용</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
constexpr bool is_pointer_v = std::is_pointer&lt;T&gt;::value;

int main()
{
    std::cout &lt;&lt; is_pointer_v&lt;int*&gt; &lt;&lt; std::endl;    // 1 (true)
    std::cout &lt;&lt; is_pointer_v&lt;int&gt; &lt;&lt; std::endl;     // 0 (false)

    return 0;
}</code></pre>
<h3 id="🔸-concepts-c20">🔸 Concepts (C++20)</h3>
<p><strong>Concepts란?</strong></p>
<ul>
<li>템플릿 매개변수에 제약 조건을 명시하는 기능</li>
<li>컴파일 에러 메시지가 명확해짐</li>
<li>템플릿 오버로딩이 쉬워짐</li>
</ul>
<p><strong>requires 키워드</strong></p>
<pre><code class="language-cpp">// C++20 이전: 모든 타입 허용
template&lt;typename T&gt;
T add(T a, T b)
{
    return a + b;
}

// C++20: 제약 추가
template&lt;typename T&gt;
requires std::is_arithmetic_v&lt;T&gt;  // 산술 타입만 허용
T add(T a, T b)
{
    return a + b;
}

int main()
{
    add(1, 2);        // ✅ OK
    add(1.5, 2.5);    // ✅ OK
    // add(&quot;a&quot;, &quot;b&quot;); // ❌ 컴파일 에러! (명확한 에러 메시지)

    return 0;
}</code></pre>
<p><strong>표준 Concepts</strong></p>
<pre><code class="language-cpp">#include &lt;concepts&gt;

// std::integral: 정수 타입만
template&lt;std::integral T&gt;
T multiply(T a, T b)
{
    return a * b;
}

// std::floating_point: 부동소수점 타입만
template&lt;std::floating_point T&gt;
T divide(T a, T b)
{
    return a / b;
}

int main()
{
    multiply(2, 3);      // ✅ OK (int)
    // multiply(2.5, 3); // ❌ 에러! (double은 integral이 아님)

    divide(5.0, 2.0);    // ✅ OK (double)
    // divide(5, 2);     // ❌ 에러! (int는 floating_point가 아님)

    return 0;
}</code></pre>
<p><strong>사용자 정의 Concept</strong></p>
<pre><code class="language-cpp">// Concept 정의
template&lt;typename T&gt;
concept Printable = requires(T t)
{
    { std::cout &lt;&lt; t } -&gt; std::same_as&lt;std::ostream&amp;&gt;;
};

// Concept 사용
template&lt;Printable T&gt;
void print(const T&amp; value)
{
    std::cout &lt;&lt; value &lt;&lt; std::endl;
}

class MyClass
{
public:
    friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const MyClass&amp; obj)
    {
        os &lt;&lt; &quot;MyClass&quot;;
        return os;
    }
};

int main()
{
    print(42);          // ✅ OK
    print(&quot;hello&quot;);     // ✅ OK
    print(MyClass{});   // ✅ OK (operator&lt;&lt; 정의됨)

    return 0;
}</code></pre>
<p><strong>Concept 조합 (논리 연산자)</strong></p>
<pre><code class="language-cpp">template&lt;typename T&gt;
concept Numeric = std::is_arithmetic_v&lt;T&gt;;

template&lt;typename T&gt;
concept Pointer = std::is_pointer_v&lt;T&gt;;

// AND 연산
template&lt;typename T&gt;
concept NumericPointer = Numeric&lt;T&gt; &amp;&amp; Pointer&lt;T&gt;;

// OR 연산
template&lt;typename T&gt;
concept NumericOrPointer = Numeric&lt;T&gt; || Pointer&lt;T&gt;;

// NOT 연산
template&lt;typename T&gt;
concept NotPointer = !Pointer&lt;T&gt;;

template&lt;NumericOrPointer T&gt;
void process(T value)
{
    // Numeric이거나 Pointer인 타입만 허용
}</code></pre>
<p><strong>requires 표현식의 다양한 형태</strong></p>
<pre><code class="language-cpp">// 1. Simple requirement (단순 요구사항)
template&lt;typename T&gt;
concept HasSize = requires(T t)
{
    t.size();  // size() 메서드가 있어야 함
};

// 2. Type requirement (타입 요구사항)
template&lt;typename T&gt;
concept HasValueType = requires
{
    typename T::value_type;  // value_type이라는 중첩 타입이 있어야 함
};

// 3. Compound requirement (복합 요구사항)
template&lt;typename T&gt;
concept Container = requires(T t)
{
    { t.begin() } -&gt; std::same_as&lt;typename T::iterator&gt;;
    { t.end() } -&gt; std::same_as&lt;typename T::iterator&gt;;
    { t.size() } -&gt; std::convertible_to&lt;size_t&gt;;
};

// 4. Nested requirement (중첩 요구사항)
template&lt;typename T&gt;
concept Sortable = requires(T t)
{
    requires std::is_same_v&lt;decltype(t &lt; t), bool&gt;;
};</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>템플릿 기본</strong></p>
<ul>
<li>컴파일 타임에 타입이 결정되는 제네릭 프로그래밍</li>
<li>사용 시점에 인스턴스화 (Instantiation)</li>
<li>Function overloading보다 효율적</li>
</ul>
<p><strong>타입 추론과 Perfect Forwarding</strong></p>
<ul>
<li>Universal Reference (<code>T&amp;&amp;</code>)는 L-value와 R-value 모두 받음</li>
<li><code>std::forward</code>로 원래 값 카테고리 유지 (Perfect Forwarding)</li>
<li><code>std::move</code>는 항상 R-value로 변환</li>
</ul>
<p><strong>템플릿 종류</strong></p>
<ul>
<li><strong>Function Template</strong>: 함수 템플릿</li>
<li><strong>Class Template</strong>: 클래스 템플릿</li>
<li><strong>Variable Template</strong>: 변수 템플릿 (C++14)</li>
<li><strong>Alias Template</strong>: 템플릿 별칭</li>
</ul>
<p><strong>고급 기능</strong></p>
<ul>
<li><strong>Variadic Templates</strong>: 가변 인자 템플릿</li>
<li><strong>Non-Type Parameters</strong>: 비타입 매개변수</li>
<li><strong>Concepts (C++20)</strong>: 템플릿 제약 조건</li>
</ul>
<p><strong>템플릿 빌드</strong></p>
<ul>
<li>헤더 파일에 구현 필수</li>
<li>또는 명시적 인스턴스화 사용</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/templates">C++ Templates</a>
<a href="https://en.cppreference.com/w/cpp/language/constraints">C++ Concepts</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 스마트 포인터 (Smart Pointers)]]></title>
            <link>https://velog.io/@sehee-jj/C-%EC%8A%A4%EB%A7%88%ED%8A%B8-%ED%8F%AC%EC%9D%B8%ED%84%B0-Smart-Pointers</link>
            <guid>https://velog.io/@sehee-jj/C-%EC%8A%A4%EB%A7%88%ED%8A%B8-%ED%8F%AC%EC%9D%B8%ED%84%B0-Smart-Pointers</guid>
            <pubDate>Mon, 05 Jan 2026 13:11:51 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-스마트-포인터-smart-pointers">📚 C++ 스마트 포인터 (Smart Pointers)</h2>
<h3 id="🔸-스마트-포인터가-필요한-이유">🔸 스마트 포인터가 필요한 이유</h3>
<p><strong>Raw Pointer의 문제점</strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    Cat() : mAge{0}
    {
        std::cout &lt;&lt; &quot;Cat constructor&quot; &lt;&lt; std::endl;
    }
    ~Cat()
    {
        std::cout &lt;&lt; &quot;Cat destructor&quot; &lt;&lt; std::endl;
    }
private:
    int mAge;
};

void foo(Cat* ptr)
{
    Cat* fooPtr = ptr;
    delete fooPtr;  // ptr이 가리키는 객체 해제
}

int main()
{
    Cat* catPtr = new Cat();
    Cat* catPtr1 = catPtr;  // 같은 객체를 가리킴

    foo(catPtr);   // 객체 해제 (1번)
    delete catPtr; // ❌ 이미 해제된 객체를 다시 해제! (Double Delete)
    // Undefined Behavior 발생!

    return 0;
}</code></pre>
<p><strong>발생 가능한 문제들</strong></p>
<ol>
<li><strong>Memory Leak</strong>: <code>delete</code>를 잊어버림</li>
<li><strong>Double Delete</strong>: 같은 메모리를 두 번 해제</li>
<li><strong>Dangling Pointer</strong>: 해제된 메모리를 가리키는 포인터</li>
<li><strong>소유권 불명확</strong>: 누가 메모리를 해제해야 하는지 모호함</li>
</ol>
<h3 id="🔸-raii-resource-acquisition-is-initialization">🔸 RAII (Resource Acquisition Is Initialization)</h3>
<p><strong>RAII란?</strong></p>
<ul>
<li>자원의 획득은 초기화와 함께 이루어져야 한다는 원칙</li>
<li>객체의 생성자에서 자원을 획득하고, 소멸자에서 자동으로 해제</li>
<li>C++의 핵심 디자인 패턴</li>
</ul>
<p><strong>RAII를 통한 자동 메모리 관리</strong></p>
<pre><code class="language-cpp">{
    std::unique_ptr&lt;Cat&gt; catPtr = std::make_unique&lt;Cat&gt;();
    // Cat 객체 생성 및 소유권 설정

    // catPtr 사용...

}  // ✅ 스코프를 벗어나면 자동으로 delete 호출!
   // 명시적 delete 불필요</code></pre>
<p><strong>스마트 포인터의 장점</strong></p>
<ul>
<li>자동 메모리 관리 (RAII)</li>
<li>소유권 명확화</li>
<li>예외 안전성 보장</li>
<li>Double Delete 방지</li>
</ul>
<h3 id="🔸-unique_ptr-exclusive-ownership">🔸 unique_ptr (Exclusive Ownership)</h3>
<p><strong>unique_ptr이란?</strong></p>
<ul>
<li><strong>독점적 소유권</strong>을 가지는 스마트 포인터</li>
<li>복사 불가능, 이동만 가능</li>
<li>가장 가볍고 효율적인 스마트 포인터 (오버헤드 거의 없음)</li>
</ul>
<p><strong>기본 사용법</strong></p>
<pre><code class="language-cpp">#include &lt;memory&gt;

int main()
{
    // ✅ 생성 방법 (C++14 이상)
    std::unique_ptr&lt;Cat&gt; catPtr = std::make_unique&lt;Cat&gt;();

    // C++11 방식
    std::unique_ptr&lt;Cat&gt; catPtr2{new Cat()};

    // 사용
    catPtr-&gt;someMethod();
    (*catPtr).someMethod();

    // ✅ 자동으로 delete 호출됨
    return 0;
}</code></pre>
<p><strong>복사 불가능, 이동 가능</strong></p>
<pre><code class="language-cpp">std::unique_ptr&lt;Cat&gt; catPtr1 = std::make_unique&lt;Cat&gt;();

// ❌ 복사 불가능
std::unique_ptr&lt;Cat&gt; catPtr2 = catPtr1;  // 컴파일 에러!

// ✅ 이동 가능 (소유권 이전)
std::unique_ptr&lt;Cat&gt; catPtr3 = std::move(catPtr1);
// catPtr1은 이제 nullptr
// catPtr3이 객체의 소유권을 가짐</code></pre>
<p><strong>함수 인자로 전달</strong></p>
<pre><code class="language-cpp">// ❌ 값으로 전달 (소유권 이전, 원본은 nullptr)
void foo(std::unique_ptr&lt;Cat&gt; ptr)
{
    // ptr이 소유권을 가짐
}  // 여기서 객체 자동 해제

// ✅ 참조로 전달 (소유권 유지, 일시적 사용)
void bar(const std::unique_ptr&lt;Cat&gt;&amp; ptr)
{
    // ptr 사용
}  // 소유권은 호출자가 계속 유지

// ✅ Raw 포인터로 전달 (소유권과 무관하게 사용)
void baz(Cat* ptr)
{
    // ptr 사용
}

int main()
{
    auto catPtr = std::make_unique&lt;Cat&gt;();

    // foo(catPtr);  // ❌ 복사 불가
    foo(std::move(catPtr));  // ✅ 소유권 이전, catPtr은 nullptr

    auto catPtr2 = std::make_unique&lt;Cat&gt;();
    bar(catPtr2);  // ✅ 소유권 유지
    baz(catPtr2.get());  // ✅ Raw 포인터 전달

    return 0;
}</code></pre>
<h3 id="🔸-unique_ptr을-멤버-변수로-사용">🔸 unique_ptr을 멤버 변수로 사용</h3>
<p><strong>클래스 멤버로 사용의 장점</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_unique&lt;Cat&gt;()} {}

    // ✅ 소멸자에서 자동으로 delete 호출됨
    ~Zoo() = default;  // 명시적 delete 불필요!

private:
    std::unique_ptr&lt;Cat&gt; mCat;  // 자동 메모리 관리
};</code></pre>
<p><strong>주의사항: Copy 불가능</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_unique&lt;Cat&gt;()} {}

private:
    std::unique_ptr&lt;Cat&gt; mCat;
};

int main()
{
    Zoo zoo1;
    Zoo zoo2 = zoo1;  // ❌ 컴파일 에러!
    // unique_ptr은 복사 불가능하므로
    // default copy constructor가 delete됨

    return 0;
}</code></pre>
<p><strong>해결 방법 1: Copy Constructor/Assignment Delete</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_unique&lt;Cat&gt;()} {}

    // ✅ 명시적으로 복사 금지
    Zoo(const Zoo&amp;) = delete;
    Zoo&amp; operator=(const Zoo&amp;) = delete;

    // ✅ 이동은 허용
    Zoo(Zoo&amp;&amp;) = default;
    Zoo&amp; operator=(Zoo&amp;&amp;) = default;

private:
    std::unique_ptr&lt;Cat&gt; mCat;
};</code></pre>
<p><strong>해결 방법 2: Deep Copy 구현</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_unique&lt;Cat&gt;()} {}

    // Deep copy constructor
    Zoo(const Zoo&amp; other)
        : mCat{other.mCat ? std::make_unique&lt;Cat&gt;(*other.mCat) : nullptr}
    {
    }

    // Deep copy assignment
    Zoo&amp; operator=(const Zoo&amp; other)
    {
        if (this != &amp;other)
        {
            mCat = other.mCat ? std::make_unique&lt;Cat&gt;(*other.mCat) : nullptr;
        }
        return *this;
    }

private:
    std::unique_ptr&lt;Cat&gt; mCat;
};</code></pre>
<p><strong>unique_ptr 사용 가이드라인</strong></p>
<ul>
<li><strong>기본 선택</strong>: 포인터가 필요하면 먼저 <code>unique_ptr</code> 고려</li>
<li><strong>소유권 명확</strong>: 한 객체가 자원을 독점적으로 소유</li>
<li><strong>성능</strong>: Raw 포인터와 동일한 성능 (오버헤드 없음)</li>
<li><strong>배열 지원</strong>: <code>std::unique_ptr&lt;int[]&gt;</code> (하지만 <code>std::vector</code> 권장)</li>
</ul>
<h3 id="🔸-shared_ptr-shared-ownership">🔸 shared_ptr (Shared Ownership)</h3>
<p><strong>shared_ptr이란?</strong></p>
<ul>
<li><strong>공유 소유권</strong>을 가지는 스마트 포인터</li>
<li>참조 카운팅(Reference Counting)을 통해 관리</li>
<li>마지막 <code>shared_ptr</code>이 소멸될 때 객체 자동 해제</li>
</ul>
<p><strong>기본 사용법</strong></p>
<pre><code class="language-cpp">#include &lt;memory&gt;

int main()
{
    // ✅ 생성 (C++11 이상)
    std::shared_ptr&lt;Cat&gt; catPtr1 = std::make_shared&lt;Cat&gt;();

    {
        // 복사 가능 (참조 카운트 증가)
        std::shared_ptr&lt;Cat&gt; catPtr2 = catPtr1;  // ✅ OK
        std::shared_ptr&lt;Cat&gt; catPtr3 = catPtr1;

        // 참조 카운트 확인
        std::cout &lt;&lt; catPtr1.use_count() &lt;&lt; std::endl;  // 3

    }  // catPtr2, catPtr3 소멸 (참조 카운트 감소)

    std::cout &lt;&lt; catPtr1.use_count() &lt;&lt; std::endl;  // 1

    return 0;
}  // catPtr1 소멸, 참조 카운트 0 → Cat 객체 자동 해제</code></pre>
<p><strong>참조 카운팅 동작 원리</strong></p>
<pre><code class="language-cpp">std::shared_ptr&lt;Cat&gt; ptr1 = std::make_shared&lt;Cat&gt;();  // count: 1
std::shared_ptr&lt;Cat&gt; ptr2 = ptr1;   // count: 2 (복사)
std::shared_ptr&lt;Cat&gt; ptr3 = ptr2;   // count: 3 (복사)

ptr3.reset();  // count: 2 (ptr3이 소유권 포기)
ptr2 = nullptr;  // count: 1
// ptr1만 남음

// ptr1이 소멸되면 count: 0 → 객체 자동 해제</code></pre>
<p><strong>Control Block</strong></p>
<pre><code>shared_ptr 구조:
┌───────────────┐
│ Data Pointer │ → Cat 객체
├───────────────┤
│ Control Block│ → ┌──────────────────┐
└───────────────┘   │ Reference Count │
                   │ Weak Count      │
                   │ Deleter         │
                   └──────────────────┘</code></pre><ul>
<li><code>shared_ptr</code>은 실제 객체 외에 <strong>Control Block</strong>이라는 추가 메모리 필요</li>
<li>Control Block에는 참조 카운트와 관리 정보 저장</li>
<li>메모리 오버헤드: 포인터 8 bytes + Control Block 약 16-24 bytes</li>
</ul>
<h3 id="🔸-shared_ptr을-멤버-변수로-사용-시-주의사항">🔸 shared_ptr을 멤버 변수로 사용 시 주의사항</h3>
<p><strong>문제 상황: 얕은 복사</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_shared&lt;Cat&gt;()} {}

    // Default copy constructor 사용
    // Zoo(const Zoo&amp; other) = default;

private:
    std::shared_ptr&lt;Cat&gt; mCat;
};

int main()
{
    Zoo zoo1;
    Zoo zoo2 = zoo1;  // ✅ 복사 가능 (shared_ptr은 복사 가능)

    // ❌ 문제: zoo1과 zoo2가 같은 Cat 객체를 공유!
    // zoo1에서 Cat을 수정하면 zoo2에도 영향

    return 0;
}</code></pre>
<p><strong>해결 방법 1: 주석으로 명시</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_shared&lt;Cat&gt;()} {}

    // ⚠️ Warning: This class uses shared_ptr.
    // Copy constructor performs shallow copy.
    // Both instances will share the same Cat object.

private:
    std::shared_ptr&lt;Cat&gt; mCat;
};</code></pre>
<p><strong>해결 방법 2: Deep Copy 함수 제공</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_shared&lt;Cat&gt;()} {}

    // Shallow copy는 default 사용
    Zoo(const Zoo&amp;) = default;
    Zoo&amp; operator=(const Zoo&amp;) = default;

    // ✅ Deep copy를 위한 별도 함수 제공
    Zoo deepCopy() const
    {
        Zoo newZoo;
        newZoo.mCat = std::make_shared&lt;Cat&gt;(*mCat);  // 새로운 Cat 객체 생성
        return newZoo;
    }

private:
    std::shared_ptr&lt;Cat&gt; mCat;
};

int main()
{
    Zoo zoo1;
    Zoo zoo2 = zoo1;           // Shallow copy
    Zoo zoo3 = zoo1.deepCopy(); // Deep copy ✅

    return 0;
}</code></pre>
<p><strong>해결 방법 3: Deep Copy를 기본으로 구현</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo() : mCat{std::make_shared&lt;Cat&gt;()} {}

    // Deep copy constructor
    Zoo(const Zoo&amp; other)
        : mCat{other.mCat ? std::make_shared&lt;Cat&gt;(*other.mCat) : nullptr}
    {
    }

    // Deep copy assignment
    Zoo&amp; operator=(const Zoo&amp; other)
    {
        if (this != &amp;other)
        {
            mCat = other.mCat ? std::make_shared&lt;Cat&gt;(*other.mCat) : nullptr;
        }
        return *this;
    }

private:
    std::shared_ptr&lt;Cat&gt; mCat;
};</code></pre>
<h3 id="🔸-순환-참조-문제-circular-reference">🔸 순환 참조 문제 (Circular Reference)</h3>
<p><strong>Memory Leak 발생 상황</strong></p>
<pre><code class="language-cpp">class Node
{
public:
    std::shared_ptr&lt;Node&gt; next;  // 다음 노드를 가리킴

    ~Node()
    {
        std::cout &lt;&lt; &quot;Node destroyed&quot; &lt;&lt; std::endl;
    }
};

int main()
{
    auto node1 = std::make_shared&lt;Node&gt;();
    auto node2 = std::make_shared&lt;Node&gt;();

    node1-&gt;next = node2;  // node1 → node2 (node2 count: 2)
    node2-&gt;next = node1;  // node2 → node1 (node1 count: 2)

    // ❌ 순환 참조 발생!
    // node1, node2가 스코프를 벗어나도
    // 서로를 참조하고 있어서 count가 0이 되지 않음
    // → Memory Leak!

    return 0;
}
// &quot;Node destroyed&quot; 출력되지 않음!</code></pre>
<p><strong>순환 참조 메모리 구조</strong></p>
<pre><code>node1 (count: 2) → next → node2 (count: 2)
  ↑                         ↓
  └────────── next ──────────┘

main 함수 종료 후:
node1 (count: 1) → next → node2 (count: 1)
  ↑                         ↓
  └────────── next ──────────┘

count가 모두 1이므로 해제되지 않음!</code></pre><p><strong>다른 언어의 해결 방법: Garbage Collection</strong></p>
<pre><code class="language-cpp">// Java, C#, Python 등
// GC가 Mark and Sweep 알고리즘으로 순환 참조 감지 및 해제
// 1. Mark: 루트에서 도달 가능한 객체 표시
// 2. Sweep: 표시되지 않은 객체 해제</code></pre>
<p>C++에는 GC가 없으므로 <code>weak_ptr</code>로 해결해야 함</p>
<h3 id="🔸-weak_ptr-순환-참조-해결">🔸 weak_ptr (순환 참조 해결)</h3>
<p><strong>weak_ptr이란?</strong></p>
<ul>
<li>참조 카운팅에 영향을 주지 않는 약한 참조</li>
<li>순환 참조 문제 해결</li>
<li>객체를 관찰(observe)만 하고, 소유하지 않음</li>
<li>사용 전에 <code>shared_ptr</code>로 변환 필요</li>
</ul>
<p><strong>순환 참조 해결</strong></p>
<pre><code class="language-cpp">class Node
{
public:
    std::shared_ptr&lt;Node&gt; next;     // 강한 참조
    std::weak_ptr&lt;Node&gt; prev;       // ✅ 약한 참조 (카운트 증가 X)

    ~Node()
    {
        std::cout &lt;&lt; &quot;Node destroyed&quot; &lt;&lt; std::endl;
    }
};

int main()
{
    auto node1 = std::make_shared&lt;Node&gt;();
    auto node2 = std::make_shared&lt;Node&gt;();

    node1-&gt;next = node2;    // node1 → node2 (node2 count: 2)
    node2-&gt;prev = node1;    // node2 ⇢ node1 (node1 count: 1, weak!)

    // ✅ 순환 참조 해결!
    // node1 count: 1, node2 count: 2

    return 0;
}
// node1 소멸 → node1 count: 0 → node1 해제
// node2 소멸 → node2 count: 0 → node2 해제
// &quot;Node destroyed&quot; 두 번 출력됨! ✅</code></pre>
<p><strong>weak_ptr 사용 방법</strong></p>
<pre><code class="language-cpp">class Observer
{
public:
    void observe(std::shared_ptr&lt;Cat&gt; cat)
    {
        mWeakCat = cat;  // weak_ptr에 저장 (카운트 증가 X)
    }

    void check()
    {
        // weak_ptr을 shared_ptr로 변환 (lock)
        if (auto sharedCat = mWeakCat.lock())  // ✅ 객체가 존재하면
        {
            // 안전하게 사용
            sharedCat-&gt;meow();
        }
        else
        {
            std::cout &lt;&lt; &quot;Cat is gone!&quot; &lt;&lt; std::endl;  // ❌ 객체가 이미 해제됨
        }
    }

private:
    std::weak_ptr&lt;Cat&gt; mWeakCat;  // 약한 참조
};

int main()
{
    Observer observer;

    {
        auto cat = std::make_shared&lt;Cat&gt;();
        observer.observe(cat);
        observer.check();  // ✅ &quot;Cat exists&quot;

    }  // cat 소멸

    observer.check();  // ❌ &quot;Cat is gone!&quot;

    return 0;
}</code></pre>
<p><strong>weak_ptr 주요 메서드</strong></p>
<pre><code class="language-cpp">std::weak_ptr&lt;Cat&gt; weakPtr;

// shared_ptr로 변환 (객체가 존재하면 shared_ptr 반환, 아니면 nullptr)
std::shared_ptr&lt;Cat&gt; sharedPtr = weakPtr.lock();

// 객체가 해제되었는지 확인
bool expired = weakPtr.expired();  // true면 객체 해제됨

// 참조 카운트 확인
long count = weakPtr.use_count();  // shared_ptr의 참조 카운트</code></pre>
<h3 id="🔸-스마트-포인터-선택-가이드">🔸 스마트 포인터 선택 가이드</h3>
<p><strong>언제 어떤 스마트 포인터를 사용할까?</strong></p>
<table>
<thead>
<tr>
<th>상황</th>
<th>추천 포인터</th>
</tr>
</thead>
<tbody><tr>
<td><strong>기본적으로</strong></td>
<td><code>unique_ptr</code></td>
</tr>
<tr>
<td>소유권이 명확한 경우</td>
<td><code>unique_ptr</code></td>
</tr>
<tr>
<td>소유권을 공유해야 하는 경우</td>
<td><code>shared_ptr</code></td>
</tr>
<tr>
<td>순환 참조 방지</td>
<td><code>weak_ptr</code></td>
</tr>
<tr>
<td>캐시 구현</td>
<td><code>weak_ptr</code></td>
</tr>
<tr>
<td>Observer 패턴</td>
<td><code>weak_ptr</code></td>
</tr>
<tr>
<td>배열 관리</td>
<td><code>std::vector</code> (스마트 포인터보다 권장)</td>
</tr>
</tbody></table>
<p><strong>일반적인 사용 패턴</strong></p>
<pre><code class="language-cpp">// ✅ 1순위: unique_ptr (독점 소유)
std::unique_ptr&lt;Cat&gt; catPtr = std::make_unique&lt;Cat&gt;();

// ✅ 2순위: shared_ptr (공유 필요)
std::shared_ptr&lt;Cat&gt; catPtr = std::make_shared&lt;Cat&gt;();

// ✅ 3순위: weak_ptr (순환 참조 방지, 관찰만)
std::weak_ptr&lt;Cat&gt; weakCat = sharedCat;

// ❌ Raw pointer는 최후의 수단
Cat* rawPtr = new Cat();  // 피하기!
delete rawPtr;</code></pre>
<p><strong>성능 비교</strong></p>
<pre><code class="language-cpp">// Raw pointer: 8 bytes
Cat* raw;

// unique_ptr: 8 bytes (오버헤드 없음)
std::unique_ptr&lt;Cat&gt; unique;

// shared_ptr: 16 bytes (포인터 8 + Control Block 포인터 8)
//             + Control Block 16-24 bytes
std::shared_ptr&lt;Cat&gt; shared;

// weak_ptr: 16 bytes (shared_ptr과 동일)
std::weak_ptr&lt;Cat&gt; weak;</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>unique_ptr</strong></p>
<ul>
<li><strong>독점 소유권</strong>, 복사 불가, 이동 가능</li>
<li>오버헤드 없음, 가장 효율적</li>
<li>멤버 변수로 사용 시 복사 생성자 주의</li>
<li><strong>기본 선택지</strong></li>
</ul>
<p><strong>shared_ptr</strong></p>
<ul>
<li><strong>공유 소유권</strong>, 참조 카운팅</li>
<li>복사 가능, 메모리 오버헤드 있음</li>
<li>순환 참조 주의 (Memory Leak 가능)</li>
<li>멤버 변수로 사용 시 얕은 복사 주의</li>
</ul>
<p><strong>weak_ptr</strong></p>
<ul>
<li>참조 카운팅에 영향 없음</li>
<li>순환 참조 해결</li>
<li>사용 전 <code>lock()</code>으로 <code>shared_ptr</code> 변환 필요</li>
<li>Observer 패턴, 캐시 구현에 유용</li>
</ul>
<p><strong>Best Practices</strong></p>
<ol>
<li><strong>Raw pointer 대신 스마트 포인터 사용</strong></li>
<li><strong>기본은 unique_ptr, 필요시 shared_ptr</strong></li>
<li><strong>순환 참조는 weak_ptr로 해결</strong></li>
<li><strong>make_unique/make_shared 사용 권장</strong></li>
<li><strong>멤버 변수로 사용 시 복사 의미 명확히</strong></li>
</ol>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/memory">C++ Smart Pointers</a>
<a href="https://en.cppreference.com/w/cpp/language/raii">RAII</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 다중 상속과 상속의 주의사항]]></title>
            <link>https://velog.io/@sehee-jj/C-%EB%8B%A4%EC%A4%91-%EC%83%81%EC%86%8D%EA%B3%BC-%EC%83%81%EC%86%8D%EC%9D%98-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD</link>
            <guid>https://velog.io/@sehee-jj/C-%EB%8B%A4%EC%A4%91-%EC%83%81%EC%86%8D%EA%B3%BC-%EC%83%81%EC%86%8D%EC%9D%98-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD</guid>
            <pubDate>Mon, 05 Jan 2026 07:24:12 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-다중-상속과-상속의-주의사항">📚 C++ 다중 상속과 상속의 주의사항</h2>
<h3 id="🔸-multiple-inheritance-다중-상속">🔸 Multiple Inheritance (다중 상속)</h3>
<p><strong>다중 상속이란?</strong></p>
<ul>
<li>하나의 클래스가 여러 부모 클래스를 동시에 상속받는 것</li>
<li>C++에서는 가능하지만 Java, C#에서는 금지됨 (인터페이스만 다중 구현 가능)</li>
</ul>
<p><strong>기본 문법</strong></p>
<pre><code class="language-cpp">class Lion : public Animal
{
public:
    void roar() { std::cout &lt;&lt; &quot;Roar!&quot; &lt;&lt; std::endl; }
};

class Tiger : public Animal
{
public:
    void growl() { std::cout &lt;&lt; &quot;Growl!&quot; &lt;&lt; std::endl; }
};

class Liger : public Lion, public Tiger  // 다중 상속
{
public:
    void speak() { std::cout &lt;&lt; &quot;Liger sound&quot; &lt;&lt; std::endl; }
};</code></pre>
<h3 id="🔸-diamond-problem-다이아몬드-문제">🔸 Diamond Problem (다이아몬드 문제)</h3>
<p><strong>문제 상황</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    Animal() { std::cout &lt;&lt; &quot;Animal constructor&quot; &lt;&lt; std::endl; }
    virtual ~Animal() { std::cout &lt;&lt; &quot;Animal destructor&quot; &lt;&lt; std::endl; }
    void breathe() { std::cout &lt;&lt; &quot;Breathing&quot; &lt;&lt; std::endl; }
protected:
    int age;
};

class Lion : public Animal
{
public:
    Lion() { std::cout &lt;&lt; &quot;Lion constructor&quot; &lt;&lt; std::endl; }
};

class Tiger : public Animal
{
public:
    Tiger() { std::cout &lt;&lt; &quot;Tiger constructor&quot; &lt;&lt; std::endl; }
};

class Liger : public Lion, public Tiger
{
public:
    Liger() { std::cout &lt;&lt; &quot;Liger constructor&quot; &lt;&lt; std::endl; }
};

int main()
{
    Liger liger;
    // 출력:
    // Animal constructor  ← Lion을 위한 Animal
    // Lion constructor
    // Animal constructor  ← Tiger를 위한 Animal (중복!)
    // Tiger constructor
    // Liger constructor

    // liger.breathe();  // ❌ 컴파일 에러! Lion의 breathe? Tiger의 breathe?
    // liger.age = 5;    // ❌ 컴파일 에러! 어느 Animal의 age?

    return 0;
}</code></pre>
<p><strong>문제점:</strong></p>
<ol>
<li><code>Animal</code> 생성자가 <strong>2번 호출됨</strong> (메모리 낭비)</li>
<li><code>Liger</code> 객체 안에 <strong>Animal이 2개 존재</strong> (모호성)</li>
<li><code>Animal</code>의 멤버에 접근할 때 어느 것인지 불명확</li>
</ol>
<p><strong>메모리 구조 (일반 다중 상속)</strong></p>
<pre><code>Liger 객체:
┌────────────────┐
│ Lion 부분      │
│  - Animal     │  ← 첫 번째 Animal
│  - Lion 데이터 │
├─────────────────┤
│ Tiger 부분     │
│  - Animal     │  ← 두 번째 Animal (중복!)
│  - Tiger 데이터│
├─────────────────┤
│ Liger 데이터    │
└─────────────────┘</code></pre><h3 id="🔸-virtual-inheritance-가상-상속">🔸 Virtual Inheritance (가상 상속)</h3>
<p><strong>해결 방법: Virtual Inheritance 사용</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    Animal() { std::cout &lt;&lt; &quot;Animal constructor&quot; &lt;&lt; std::endl; }
    virtual ~Animal() { std::cout &lt;&lt; &quot;Animal destructor&quot; &lt;&lt; std::endl; }
    virtual void speak() = 0;
protected:
    double animalData;  // 8 bytes
};

class Lion : public virtual Animal  // ✅ virtual 키워드 추가
{
public:
    Lion() { std::cout &lt;&lt; &quot;Lion constructor&quot; &lt;&lt; std::endl; }
    void speak() override { std::cout &lt;&lt; &quot;Roar!&quot; &lt;&lt; std::endl; }
protected:
    double lionData;  // 8 bytes
};

class Tiger : public virtual Animal  // ✅ virtual 키워드 추가
{
public:
    Tiger() { std::cout &lt;&lt; &quot;Tiger constructor&quot; &lt;&lt; std::endl; }
    void speak() override { std::cout &lt;&lt; &quot;Growl!&quot; &lt;&lt; std::endl; }
protected:
    double tigerData;  // 8 bytes
};

class Liger : public Lion, public Tiger
{
public:
    Liger() { std::cout &lt;&lt; &quot;Liger constructor&quot; &lt;&lt; std::endl; }
    void speak() override { std::cout &lt;&lt; &quot;Liger sound&quot; &lt;&lt; std::endl; }
};

int main()
{
    Liger liger;
    // 출력:
    // Animal constructor  ← 단 1번만 호출됨! ✅
    // Lion constructor
    // Tiger constructor
    // Liger constructor

    liger.speak();     // ✅ OK - &quot;Liger sound&quot;
    // liger.animalData = 5.0;  // ✅ 모호성 해결! (protected라 직접 접근은 불가)

    return 0;
}</code></pre>
<p><strong>가상 상속의 효과</strong></p>
<ul>
<li><code>Animal</code> 생성자가 <strong>단 1번만 호출</strong>됨</li>
<li><code>Liger</code> 객체 안에 <code>Animal</code>이 <strong>1개만 존재</strong></li>
<li>멤버 접근 모호성 해결</li>
</ul>
<h3 id="🔸-메모리-레이아웃-비교">🔸 메모리 레이아웃 비교</h3>
<p><strong>일반 상속 vs 가상 상속의 메모리 크기 변화</strong></p>
<p><strong>1. 일반 상속 (Lion만 있을 때)</strong></p>
<pre><code class="language-cpp">class Lion : public Animal
{
protected:
    double lionData;  // 8 bytes
};

// sizeof(Lion) = 24 bytes
// [vptr: 8] [animalData: 8] [lionData: 8]</code></pre>
<p><strong>2. 가상 상속 (Lion만 있을 때)</strong></p>
<pre><code class="language-cpp">class Lion : public virtual Animal
{
protected:
    double lionData;  // 8 bytes
};

// sizeof(Lion) = 32 bytes
// [Lion vptr: 8] [lionData: 8] [Animal vptr: 8] [animalData: 8]</code></pre>
<p><strong>메모리 구조 상세 비교</strong></p>
<pre><code>일반 상속 Lion (24 bytes):
┌─────────────┐
│ vptr (8B)  │ → VTable
├─────────────┤
│ animalData │
├─────────────┤
│ lionData   │
└─────────────┘

가상 상속 Lion (32 bytes):
┌──────────────┐
│ Lion vptr   │ → Lion VTable (offset 포함)
├──────────────┤
│ lionData    │
├──────────────┤
│ Animal vptr │ → Animal VTable
├──────────────┤
│ animalData  │
└──────────────┘</code></pre><p><strong>가상 상속이 더 큰 이유:</strong></p>
<ul>
<li><strong>VTable이 2개</strong> 필요 (Lion용, Animal용)</li>
<li>Lion VTable에 <strong>offset 정보</strong> 추가 저장</li>
<li>Animal 데이터의 위치가 동적으로 결정됨</li>
</ul>
<h3 id="🔸-thunk와-offset">🔸 Thunk와 Offset</h3>
<p><strong>일반 상속에서의 함수 호출</strong></p>
<pre><code class="language-cpp">Animal* polyLion = new Lion();
polyLion-&gt;speak();  // Lion의 speak 직접 호출</code></pre>
<ul>
<li><code>Lion</code>의 데이터 위치가 고정되어 있어서 offset 계산 불필요</li>
<li>VTable에서 함수 주소를 바로 호출</li>
</ul>
<p><strong>가상 상속에서의 함수 호출</strong></p>
<pre><code class="language-cpp">Animal* polyLion = new Lion();  // Animal 포인터로 가상 상속된 Lion 참조
polyLion-&gt;speak();</code></pre>
<p><strong>문제:</strong> <code>Animal*</code> 포인터는 객체의 <code>Animal</code> 부분을 가리킴. 하지만 가상 상속에서는 <code>Lion</code> 데이터가 <code>Animal</code> 데이터와 떨어져 있음!</p>
<p><strong>해결: Thunk 사용</strong></p>
<pre><code>Lion VTable (가상 상속):
┌─────────────────────────┐
│ offset to Animal: +16 │ ← offset 정보
├─────────────────────────┤
│ &amp;Lion::speak [thunk]  │ ← thunk 함수
└─────────────────────────┘

Thunk 함수:
1. this 포인터를 offset만큼 조정
2. 실제 Lion::speak() 호출</code></pre><p><strong>Thunk란?</strong></p>
<ul>
<li>포인터를 조정(adjust)하는 작은 함수</li>
<li><code>this</code> 포인터에 offset을 더해서 올바른 위치로 이동</li>
<li>가상 상속에서만 필요 (일반 상속에서는 불필요)</li>
</ul>
<p><strong>포인터 타입에 따른 VTable 선택</strong></p>
<pre><code class="language-cpp">Lion lion;

// Lion* 타입: offset 계산 불필요
Lion* lionPtr = &amp;lion;
lionPtr-&gt;speak();  // thunk 없는 VTable 사용 (빠름)

// Animal* 타입: offset 계산 필요
Animal* animalPtr = &amp;lion;
animalPtr-&gt;speak();  // thunk 있는 VTable 사용 (느림)</code></pre>
<p><img src="https://velog.velcdn.com/images/sehee-jj/post/c7bffd93-0c07-4101-a5e7-18028e51ff48/image.png" alt=""></p>
<p><strong>가상 상속의 성능 비용</strong></p>
<ul>
<li>VTable이 커짐 (offset 정보 + thunk 함수 포인터)</li>
<li>간접 참조가 한 단계 더 추가됨</li>
<li>메모리 오버헤드 증가</li>
</ul>
<h3 id="🔸-다중-상속-사용-지침">🔸 다중 상속 사용 지침</h3>
<p><strong>다중 상속을 피해야 하는 이유</strong></p>
<ol>
<li><strong>복잡성 증가</strong>: 코드 이해와 유지보수 어려움</li>
<li><strong>다이아몬드 문제</strong>: 가상 상속으로 해결 가능하지만 복잡도 증가</li>
<li><strong>성능 오버헤드</strong>: 가상 상속은 메모리와 성능 비용 발생</li>
<li><strong>모호성</strong>: 같은 이름의 멤버가 여러 부모에 있을 때 충돌</li>
</ol>
<p><strong>다중 상속이 허용되는 경우</strong></p>
<pre><code class="language-cpp">// ✅ OK: 여러 인터페이스 구현
class ISerializable
{
public:
    virtual std::string serialize() = 0;
    virtual ~ISerializable() = default;
};

class ILoggable
{
public:
    virtual void log() = 0;
    virtual ~ILoggable() = default;
};

class User : public ISerializable, public ILoggable
{
    // 순수 가상 함수만 있는 인터페이스는 다중 상속 OK
};</code></pre>
<p><strong>권장 설계 패턴</strong></p>
<ul>
<li><strong>인터페이스 다중 구현</strong>: ✅ 안전</li>
<li><strong>구현 클래스 다중 상속</strong>: ❌ 위험</li>
<li><strong>Composition over Inheritance</strong>: 상속 대신 포함 관계 사용</li>
</ul>
<pre><code class="language-cpp">// ❌ 나쁜 설계: 구현 클래스 다중 상속
class Liger : public Lion, public Tiger { };

// ✅ 좋은 설계: Composition 사용
class Liger : public Animal
{
    Lion lionBehavior;   // Lion을 포함
    Tiger tigerBehavior; // Tiger를 포함
};</code></pre>
<h3 id="🔸-object-slicing-객체-잘림-현상">🔸 Object Slicing (객체 잘림 현상)</h3>
<p><strong>Object Slicing이란?</strong></p>
<ul>
<li>자식 객체를 부모 타입 <strong>값(value)</strong> 으로 복사할 때 자식 부분이 잘려나가는 현상</li>
<li>포인터나 참조에서는 발생하지 않음</li>
</ul>
<p><strong>문제 상황</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() { std::cout &lt;&lt; &quot;Animal sound&quot; &lt;&lt; std::endl; }
protected:
    double weight;
};

class Cat : public Animal
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
private:
    std::string name;  // Cat만의 데이터
};

int main()
{
    Cat kitty;
    kitty.speak();  // &quot;Meow~&quot;

    // ✅ 참조: Object Slicing 없음
    Animal&amp; animalRef = kitty;
    animalRef.speak();  // &quot;Meow~&quot; (가상 함수 정상 동작)

    // ❌ 값 복사: Object Slicing 발생!
    Animal animalObj = kitty;  // Cat의 name이 잘려나감!
    animalObj.speak();  // &quot;Animal sound&quot; (Cat의 speak가 아님!)

    // sizeof 확인
    std::cout &lt;&lt; &quot;sizeof(Cat): &quot; &lt;&lt; sizeof(Cat) &lt;&lt; std::endl;      // 40 bytes
    std::cout &lt;&lt; &quot;sizeof(animalObj): &quot; &lt;&lt; sizeof(animalObj) &lt;&lt; std::endl;  // 16 bytes
    // Cat의 name이 사라짐!

    return 0;
}</code></pre>
<p><strong>Object Slicing이 발생하는 상황</strong></p>
<pre><code class="language-cpp">void feedAnimal(Animal animal)  // ❌ 값으로 받음
{
    animal.speak();  // 항상 &quot;Animal sound&quot;
}

Cat kitty;
feedAnimal(kitty);  // Object Slicing 발생!</code></pre>
<p><strong>메모리 구조</strong></p>
<p>Cat 객체 (원본):</p>
<pre><code>┌──────────┐
│ vptr    │
├──────────┤
│ weight  │
├──────────┤
│ name    │  ← Cat 고유 데이터
└──────────┘</code></pre><p>Animal animalObj = kitty 복사 후:
<img src="https://velog.velcdn.com/images/sehee-jj/post/1185dd1d-c179-4f53-86b6-e8f5bb40f038/image.png" alt=""></p>
<h3 id="🔸-object-slicing-방지-방법">🔸 Object Slicing 방지 방법</h3>
<p><strong>방법 1: Copy Constructor를 Protected로 선언</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() = 0;
    virtual ~Animal() = default;

protected:
    Animal() = default;
    Animal(const Animal&amp;) = default;  // ✅ protected 복사 생성자
    Animal&amp; operator=(const Animal&amp;) = default;
};

int main()
{
    Cat kitty;
    Animal animalObj = kitty;  // ❌ 컴파일 에러! protected라 접근 불가
    return 0;
}</code></pre>
<p><strong>방법 2: Copy Constructor를 Delete</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() = 0;
    virtual ~Animal() = default;

    Animal(const Animal&amp;) = delete;  // ✅ 복사 금지
    Animal&amp; operator=(const Animal&amp;) = delete;

protected:
    Animal() = default;
};</code></pre>
<p><strong>방법 3: Pure Abstract Class로 만들기 (가장 권장)</strong></p>
<pre><code class="language-cpp">class IAnimal  // 인터페이스
{
public:
    virtual void speak() = 0;
    virtual ~IAnimal() = default;

protected:
    IAnimal() = default;
    IAnimal(const IAnimal&amp;) = delete;
    IAnimal&amp; operator=(const IAnimal&amp;) = delete;
};

// 구체 클래스는 복사 가능
class Cat : public IAnimal
{
public:
    Cat() = default;
    Cat(const Cat&amp;) = default;  // ✅ Cat끼리는 복사 가능
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
};

int main()
{
    Cat kitty1;
    Cat kitty2 = kitty1;  // ✅ OK - Cat끼리는 복사 가능

    IAnimal* ptr = &amp;kitty1;
    // IAnimal obj = *ptr;  // ❌ 컴파일 에러! IAnimal은 추상 클래스

    return 0;
}</code></pre>
<p><strong>올바른 함수 파라미터 설계</strong></p>
<pre><code class="language-cpp">// ❌ 나쁜 설계: 값으로 받음 (Object Slicing)
void feedAnimal(Animal animal) { }

// ✅ 좋은 설계: 참조로 받음
void feedAnimal(const Animal&amp; animal) { }

// ✅ 좋은 설계: 포인터로 받음
void feedAnimal(Animal* animal) { }</code></pre>
<h3 id="🔸-상속에서의-operator-overloading-문제">🔸 상속에서의 Operator Overloading 문제</h3>
<p><strong>문제 상황</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() { std::cout &lt;&lt; &quot;Animal&quot; &lt;&lt; std::endl; }

    bool operator==(const Animal&amp; other) const
    {
        return weight == other.weight;
    }

protected:
    double weight;
};

class Cat : public Animal
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }

    // operator==를 재정의하지 않음!
private:
    std::string name;
};

int main()
{
    Cat kitty1;
    Cat kitty2;

    if (kitty1 == kitty2)  // ❌ Animal::operator==가 호출됨
    {                       // name은 비교하지 않음!
        std::cout &lt;&lt; &quot;Same cats&quot; &lt;&lt; std::endl;
    }

    return 0;
}</code></pre>
<p><strong>문제점:</strong></p>
<ul>
<li>자식 클래스에서 <code>operator==</code>를 재정의하지 않으면 부모의 연산자가 사용됨</li>
<li>자식 클래스의 추가 멤버는 비교되지 않음</li>
</ul>
<p><strong>해결 방법: 자식 클래스에서 재정의</strong></p>
<pre><code class="language-cpp">class Cat : public Animal
{
public:
    bool operator==(const Cat&amp; other) const
    {
        // 부모의 멤버도 비교
        return Animal::operator==(other) &amp;&amp; name == other.name;
    }

private:
    std::string name;
};</code></pre>
<p><strong>더 나은 해결: 가상 함수로 만들기</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual bool equals(const Animal&amp; other) const
    {
        return weight == other.weight;
    }

protected:
    double weight;
};

class Cat : public Animal
{
public:
    bool equals(const Animal&amp; other) const override
    {
        // dynamic_cast로 Cat인지 확인
        const Cat* catPtr = dynamic_cast&lt;const Cat*&gt;(&amp;other);
        if (!catPtr) return false;

        return Animal::equals(other) &amp;&amp; name == catPtr-&gt;name;
    }

private:
    std::string name;
};</code></pre>
<h3 id="🔸-dynamic-cast와-rtti">🔸 Dynamic Cast와 RTTI</h3>
<p><strong>RTTI (Run-Time Type Information)</strong></p>
<ul>
<li>런타임에 객체의 실제 타입을 확인하는 기능</li>
<li><code>dynamic_cast</code>, <code>typeid</code> 연산자 사용</li>
<li>성능 오버헤드와 설계 문제로 <strong>사용을 피하는 것이 좋음</strong></li>
</ul>
<p><strong>Upcasting (업캐스팅): 항상 안전</strong></p>
<pre><code class="language-cpp">Cat kitty;
Animal* ptr = &amp;kitty;  // ✅ OK - 자식 → 부모 (안전)</code></pre>
<p><strong>Downcasting (다운캐스팅): 위험</strong></p>
<pre><code class="language-cpp">Animal* animalPtr = getAnimal();  // 실제로 무엇인지 모름

// ❌ static_cast: 위험! 타입 체크 없음
Cat* catPtr = static_cast&lt;Cat*&gt;(animalPtr);
catPtr-&gt;meow();  // animalPtr이 실제로 Cat이 아니면 Undefined Behavior!

// ⚠️ dynamic_cast: 안전하지만 권장하지 않음
Cat* catPtr2 = dynamic_cast&lt;Cat*&gt;(animalPtr);
if (catPtr2)  // nullptr 체크 필요
{
    catPtr2-&gt;meow();  // ✅ 안전
}
else
{
    std::cout &lt;&lt; &quot;Not a Cat!&quot; &lt;&lt; std::endl;
}</code></pre>
<p><strong>dynamic_cast 동작 원리</strong></p>
<pre><code class="language-cpp">Animal* ptr = &amp;someObject;
Cat* catPtr = dynamic_cast&lt;Cat*&gt;(ptr);

// 내부 동작:
// 1. ptr이 가리키는 객체의 VTable 확인
// 2. VTable의 type_info 조회
// 3. 상속 계층 구조를 검사
// 4. Cat으로 안전하게 변환 가능하면 포인터 반환
// 5. 불가능하면 nullptr 반환</code></pre>
<p><strong>dynamic_cast를 피해야 하는 이유</strong></p>
<ol>
<li><p><strong>성능 오버헤드</strong></p>
<ul>
<li>VTable 탐색과 타입 체크 비용</li>
<li>상속 계층이 깊을수록 느려짐</li>
</ul>
</li>
<li><p><strong>설계 문제의 신호</strong></p>
<pre><code class="language-cpp">// ❌ 나쁜 설계: 타입 체크
void process(Animal* animal)
{
    if (Cat* cat = dynamic_cast&lt;Cat*&gt;(animal))
    {
        cat-&gt;meow();
    }
    else if (Dog* dog = dynamic_cast&lt;Dog*&gt;(animal))
    {
        dog-&gt;bark();
    }
    // 새로운 동물이 추가되면 이 코드를 수정해야 함!
}

// ✅ 좋은 설계: 가상 함수 사용
void process(Animal* animal)
{
    animal-&gt;speak();  // 각 클래스가 자신의 방식으로 speak 구현
}</code></pre>
</li>
<li><p><strong>Open-Closed Principle 위반</strong></p>
<ul>
<li>새로운 타입 추가 시 기존 코드 수정 필요</li>
</ul>
</li>
</ol>
<p><strong>올바른 대안: Virtual Function 사용</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void makeSound() = 0;
    virtual void play() = 0;
    virtual ~Animal() = default;
};

class Cat : public Animal
{
public:
    void makeSound() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
    void play() override { std::cout &lt;&lt; &quot;Playing with yarn&quot; &lt;&lt; std::endl; }
};

class Dog : public Animal
{
public:
    void makeSound() override { std::cout &lt;&lt; &quot;Woof!&quot; &lt;&lt; std::endl; }
    void play() override { std::cout &lt;&lt; &quot;Playing fetch&quot; &lt;&lt; std::endl; }
};

// ✅ 타입 체크 없이 처리
void interact(Animal* animal)
{
    animal-&gt;makeSound();
    animal-&gt;play();
    // 새로운 동물이 추가되어도 이 코드는 수정 불필요!
}</code></pre>
<p><strong>dynamic_cast가 허용되는 경우</strong></p>
<ul>
<li>레거시 코드와의 상호작용</li>
<li>외부 라이브러리의 제약</li>
<li>정말로 타입 정보가 필요한 특수한 경우 (예: 직렬화, 리플렉션)</li>
</ul>
<h3 id="🔸-상속-설계-best-practices">🔸 상속 설계 Best Practices</h3>
<p><strong>1. Base 클래스는 Pure Abstract Class로 만들기</strong></p>
<pre><code class="language-cpp">// ✅ 권장: 인터페이스
class IAnimal
{
public:
    virtual void speak() = 0;
    virtual void eat() = 0;
    virtual ~IAnimal() = default;

protected:
    IAnimal() = default;
    IAnimal(const IAnimal&amp;) = delete;
    IAnimal&amp; operator=(const IAnimal&amp;) = delete;
};</code></pre>
<p><strong>이유:</strong></p>
<ul>
<li>Object Slicing 방지</li>
<li>다형성 강제</li>
<li>명확한 인터페이스 정의</li>
</ul>
<p><strong>2. Composition over Inheritance</strong></p>
<pre><code class="language-cpp">// ❌ 상속 남용
class Stack : public std::vector&lt;int&gt;
{
    // vector의 모든 기능이 노출됨 (insert, erase 등)
};

// ✅ Composition 사용
class Stack
{
public:
    void push(int value) { data.push_back(value); }
    int pop() { int val = data.back(); data.pop_back(); return val; }

private:
    std::vector&lt;int&gt; data;  // 포함 관계
};</code></pre>
<p><strong>3. 가상 소멸자 필수</strong></p>
<pre><code class="language-cpp">class Base
{
public:
    virtual ~Base() = default;  // ✅ 필수!
};</code></pre>
<p><strong>4. override 키워드 사용</strong></p>
<pre><code class="language-cpp">class Derived : public Base
{
public:
    void func() override;  // ✅ 오버라이드 의도 명확히
};</code></pre>
<p><strong>5. final 키워드로 상속 제한</strong></p>
<pre><code class="language-cpp">class FinalClass final  // 더 이상 상속 불가
{
};

class Base
{
public:
    virtual void func() final;  // 이 함수는 더 이상 오버라이드 불가
};</code></pre>
<p><strong>6. 다중 상속은 인터페이스로만</strong></p>
<pre><code class="language-cpp">// ✅ OK: 인터페이스 다중 구현
class Serializable : public ISerializable, public ILoggable { };

// ❌ 피하기: 구현 클래스 다중 상속
class Liger : public Lion, public Tiger { };</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>다중 상속 관련</strong></p>
<ul>
<li><strong>다이아몬드 문제</strong>: 가상 상속으로 해결 가능하지만 복잡도와 성능 비용 증가</li>
<li><strong>가상 상속</strong>: offset과 thunk로 인한 메모리 및 성능 오버헤드</li>
<li><strong>권장</strong>: 인터페이스 다중 구현만 사용, 구현 클래스 다중 상속 피하기</li>
</ul>
<p><strong>Object Slicing</strong></p>
<ul>
<li><strong>원인</strong>: 자식 객체를 부모 타입 값으로 복사</li>
<li><strong>해결</strong>: 복사 생성자 protected/delete, Pure Abstract Class 사용</li>
<li><strong>함수 파라미터</strong>: 항상 참조나 포인터로 받기</li>
</ul>
<p><strong>Dynamic Cast와 RTTI</strong></p>
<ul>
<li><strong>피해야 하는 이유</strong>: 성능 오버헤드, 설계 문제의 신호</li>
<li><strong>대안</strong>: Virtual Function으로 다형성 구현</li>
<li><strong>허용되는 경우</strong>: 레거시 코드, 외부 라이브러리 제약</li>
</ul>
<p><strong>설계 원칙</strong></p>
<ol>
<li>Base 클래스는 Pure Abstract Class로</li>
<li>Composition over Inheritance</li>
<li>가상 소멸자 필수</li>
<li>override/final 키워드 활용</li>
<li>인터페이스 다중 구현만 허용</li>
</ol>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/derived_class">C++ Multiple Inheritance</a>
<a href="https://en.cppreference.com/w/cpp/language/derived_class">C++ Virtual Inheritance</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] Inheritance & Virtual function]]></title>
            <link>https://velog.io/@sehee-jj/C-Inheritance-Virtual-function</link>
            <guid>https://velog.io/@sehee-jj/C-Inheritance-Virtual-function</guid>
            <pubDate>Mon, 05 Jan 2026 06:40:55 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-상속과-가상-함수">📚 C++ 상속과 가상 함수</h2>
<h3 id="🔸-상속-inheritance">🔸 상속 (Inheritance)</h3>
<p><strong>상속의 목적</strong></p>
<ol>
<li><p><strong>Class Relationship (클래스 관계 표현)</strong></p>
<ul>
<li>&quot;is-a&quot; 관계를 코드로 표현</li>
<li>예: Cat is an Animal, Dog is an Animal</li>
</ul>
</li>
<li><p><strong>Code Reuse (코드 재사용)</strong></p>
<ul>
<li>공통 기능을 부모 클래스에 구현</li>
<li>자식 클래스에서 중복 코드 제거</li>
</ul>
</li>
<li><p><strong>Class Interface Consistency (일관된 인터페이스)</strong></p>
<ul>
<li>같은 부모를 상속받은 클래스들은 동일한 인터페이스 제공</li>
<li>다형성(Polymorphism) 구현의 기반</li>
</ul>
</li>
</ol>
<h3 id="🔸-상속-접근-지정자-publicprotectedprivate">🔸 상속 접근 지정자 (public/protected/private)</h3>
<p><strong>접근 지정자에 따른 멤버 접근 권한 변화</strong></p>
<pre><code class="language-cpp">class Base
{
public:
    int x;
protected:
    int y;
private:
    int z;
};</code></pre>
<p><strong>1. public 상속 (가장 일반적)</strong></p>
<pre><code class="language-cpp">class Derived : public Base
{
    // x는 public 유지
    // y는 protected 유지
    // z는 접근 불가 (private은 항상 접근 불가)
};</code></pre>
<ul>
<li>부모 클래스의 접근 지정자를 그대로 유지</li>
<li>&quot;is-a&quot; 관계를 표현할 때 사용</li>
<li><strong>대부분의 경우 public 상속을 사용함</strong></li>
</ul>
<p><strong>2. protected 상속 (드물게 사용)</strong></p>
<pre><code class="language-cpp">class Derived : protected Base
{
    // x는 protected로 변경
    // y는 protected 유지
    // z는 접근 불가
};</code></pre>
<ul>
<li>부모의 public 멤버가 protected로 변경</li>
<li>외부에서 부모 클래스의 인터페이스 접근 차단</li>
<li>&quot;implemented-in-terms-of&quot; 관계</li>
</ul>
<p><strong>3. private 상속 (클래스의 기본값)</strong></p>
<pre><code class="language-cpp">class Derived : private Base  // class는 기본값이 private
{
    // x는 private로 변경
    // y는 private로 변경
    // z는 접근 불가
};</code></pre>
<ul>
<li>모든 상속받은 멤버가 private로 변경</li>
<li>구현 상속일 때 사용 (인터페이스 상속이 아님)</li>
<li><strong>일반적으로 Composition(구성)을 사용하는 것이 더 명확함</strong></li>
</ul>
<p><strong>접근 권한 정리 표</strong></p>
<table>
<thead>
<tr>
<th>부모 클래스</th>
<th>public 상속</th>
<th>protected 상속</th>
<th>private 상속</th>
</tr>
</thead>
<tbody><tr>
<td>public</td>
<td>public</td>
<td>protected</td>
<td>private</td>
</tr>
<tr>
<td>protected</td>
<td>protected</td>
<td>protected</td>
<td>private</td>
</tr>
<tr>
<td>private</td>
<td>접근 불가</td>
<td>접근 불가</td>
<td>접근 불가</td>
</tr>
</tbody></table>
<p><strong>실전 가이드라인</strong></p>
<ul>
<li><strong>99%의 경우 public 상속을 사용</strong></li>
<li>protected/private 상속은 특수한 경우에만 사용</li>
<li>private 상속 대신 멤버 변수로 포함(Composition)하는 것을 권장</li>
</ul>
<pre><code class="language-cpp">// ❌ Private 상속 (혼란스러움)
class Car : private Engine { };

// ✅ Composition (명확함)
class Car {
    Engine engine;  // Car has an Engine
};</code></pre>
<h3 id="🔸-virtual-function-가상-함수">🔸 Virtual Function (가상 함수)</h3>
<p><strong>가상 함수가 필요한 이유</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    void speak()  // 일반 함수
    {
        std::cout &lt;&lt; &quot;Animal sound&quot; &lt;&lt; std::endl;
    }
};

class Cat : public Animal
{
public:
    void speak()  // 함수 재정의 (Overriding)
    {
        std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl;
    }
};

int main()
{
    Cat kitty;
    Animal* ptr = &amp;kitty;  // 부모 타입 포인터로 자식 객체 참조

    ptr-&gt;speak();  // ❌ &quot;Animal sound&quot; 출력 (Cat의 speak가 아님!)
    return 0;
}</code></pre>
<p><strong>문제점</strong>: 포인터 타입이 <code>Animal*</code>이므로 컴파일 타임에 <code>Animal::speak()</code>가 호출됨</p>
<p><strong>virtual 키워드로 해결</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak()  // ✅ 가상 함수로 선언
    {
        std::cout &lt;&lt; &quot;Animal sound&quot; &lt;&lt; std::endl;
    }
};

class Cat : public Animal
{
public:
    void speak() override  // override 키워드 권장
    {
        std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl;
    }
};

int main()
{
    Cat kitty;
    Animal* ptr = &amp;kitty;

    ptr-&gt;speak();  // ✅ &quot;Meow~&quot; 출력 (런타임에 실제 타입 확인!)
    return 0;
}</code></pre>
<p><strong>Dynamic Binding (동적 바인딩)</strong></p>
<ul>
<li>가상 함수는 런타임에 실제 객체의 타입을 확인하여 호출</li>
<li>이를 통해 다형성(Polymorphism) 구현</li>
</ul>
<h3 id="🔸-virtual-destructor-가상-소멸자">🔸 Virtual Destructor (가상 소멸자)</h3>
<p><strong>❗ 중요: Base 클래스의 소멸자는 반드시 virtual로 선언해야 함</strong></p>
<p><strong>문제 상황</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    Animal() { std::cout &lt;&lt; &quot;Animal constructor&quot; &lt;&lt; std::endl; }
    ~Animal() { std::cout &lt;&lt; &quot;Animal destructor&quot; &lt;&lt; std::endl; }  // ❌ 가상 소멸자가 아님
};

class Cat : public Animal
{
public:
    Cat() { std::cout &lt;&lt; &quot;Cat constructor&quot; &lt;&lt; std::endl; }
    ~Cat() { std::cout &lt;&lt; &quot;Cat destructor&quot; &lt;&lt; std::endl; }
};

int main()
{
    Animal* polyCat = new Cat();
    // 출력:
    // Animal constructor
    // Cat constructor

    delete polyCat;
    // 출력:
    // Animal destructor  ❌ Cat destructor가 호출되지 않음!
    // → 메모리 누수 발생!

    return 0;
}</code></pre>
<p><strong>해결: 가상 소멸자 사용</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    Animal() { std::cout &lt;&lt; &quot;Animal constructor&quot; &lt;&lt; std::endl; }
    virtual ~Animal() { std::cout &lt;&lt; &quot;Animal destructor&quot; &lt;&lt; std::endl; }  // ✅ virtual 추가
};

class Cat : public Animal
{
public:
    Cat() { std::cout &lt;&lt; &quot;Cat constructor&quot; &lt;&lt; std::endl; }
    ~Cat() override { std::cout &lt;&lt; &quot;Cat destructor&quot; &lt;&lt; std::endl; }
};

int main()
{
    Animal* polyCat = new Cat();
    delete polyCat;
    // 출력:
    // Cat destructor    ✅ 올바른 순서로 호출됨
    // Animal destructor

    return 0;
}</code></pre>
<p><strong>가상 소멸자 규칙</strong></p>
<ul>
<li>상속받을 가능성이 있는 클래스의 소멸자는 <strong>반드시 virtual public</strong>으로 선언</li>
<li>또는 상속을 막으려면 소멸자를 <strong>protected</strong>로 선언</li>
</ul>
<pre><code class="language-cpp">// ✅ 옵션 1: 상속 허용 + virtual 소멸자
class Base {
public:
    virtual ~Base() = default;
};

// ✅ 옵션 2: 상속 금지 (소멸자 protected)
class Base {
protected:
    ~Base() = default;  // 외부에서 delete 불가
};

// ✅ 옵션 3: C++11 final 키워드
class Base final {  // 상속 불가
public:
    ~Base() = default;
};</code></pre>
<h3 id="🔸-virtual-table-가상-함수-테이블">🔸 Virtual Table (가상 함수 테이블)</h3>
<p><strong>메모리 구조 변화</strong></p>
<p><strong>일반 함수만 있는 경우</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    void speak() { std::cout &lt;&lt; &quot;Animal&quot; &lt;&lt; std::endl; }
private:
    double height;  // 8 bytes
};

class Cat : public Animal
{
public:
    void speak() { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
private:
    double weight;  // 8 bytes
};

// sizeof(Animal) = 8 bytes  (height만)
// sizeof(Cat) = 16 bytes    (height + weight)</code></pre>
<p><strong>가상 함수가 있는 경우</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() { std::cout &lt;&lt; &quot;Animal&quot; &lt;&lt; std::endl; }
private:
    double height;  // 8 bytes
};

class Cat : public Animal
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
private:
    double weight;  // 8 bytes
};

// sizeof(Animal) = 16 bytes  (vptr 8 + height 8)
// sizeof(Cat) = 24 bytes     (vptr 8 + height 8 + weight 8)</code></pre>
<p><strong>Virtual Table (VTable) 구조</strong></p>
<pre><code>Animal 객체 메모리:
┌─────────────┐
│ vptr (8B)  │ → Animal VTable
├─────────────┤   ┌──────────────────┐
│ height (8B)│   │&amp;Animal::speak()│
└─────────────┘   └──────────────────┘

Cat 객체 메모리:
┌─────────────┐
│ vptr (8B)  │ → Cat VTable
├─────────────┤   ┌──────────────────┐
│ height (8B)│   │ &amp;Cat::speak()  │
├─────────────┤   └──────────────────┘
│ weight (8B)│
└─────────────┘</code></pre><p><strong>Virtual Table의 동작 원리</strong></p>
<ol>
<li>가상 함수가 있는 클래스는 <strong>vptr (virtual table pointer)</strong> 을 가짐 (8 bytes)</li>
<li>vptr은 해당 클래스의 <strong>VTable</strong>을 가리킴</li>
<li>VTable에는 가상 함수들의 실제 주소가 저장됨</li>
<li>가상 함수 호출 시 vptr → VTable → 실제 함수 주소로 간접 호출</li>
</ol>
<p><strong>성능 고려사항</strong></p>
<ul>
<li>가상 함수가 없는 경우: 직접 호출 (빠름)</li>
<li>가상 함수가 있는 경우: 간접 호출 (약간 느림, 약 20-30% 오버헤드)</li>
<li>메모리: 객체당 vptr 8 bytes 추가</li>
</ul>
<h3 id="🔸-부모-클래스-포인터의-제약">🔸 부모 클래스 포인터의 제약</h3>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() { std::cout &lt;&lt; &quot;Animal&quot; &lt;&lt; std::endl; }
};

class Cat : public Animal
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
    void meow() { std::cout &lt;&lt; &quot;Meow meow!&quot; &lt;&lt; std::endl; }  // Cat 전용 함수
};

int main()
{
    Cat kitty;
    Animal* ptr = &amp;kitty;

    ptr-&gt;speak();  // ✅ OK - 가상 함수이므로 Cat::speak() 호출됨
    ptr-&gt;meow();   // ❌ 컴파일 에러! Animal 타입으로는 meow() 접근 불가

    // 해결 방법 1: Cat 타입 포인터 사용
    Cat* catPtr = &amp;kitty;
    catPtr-&gt;meow();  // ✅ OK

    // 해결 방법 2: 다운캐스팅 (권장하지 않음)
    Cat* catPtr2 = static_cast&lt;Cat*&gt;(ptr);
    catPtr2-&gt;meow();  // ✅ OK (하지만 위험함)

    return 0;
}</code></pre>
<p><strong>핵심 원칙</strong></p>
<ul>
<li>부모 타입 포인터로는 <strong>부모 클래스에 선언된 멤버</strong>만 접근 가능</li>
<li>가상 함수는 런타임에 실제 타입의 함수가 호출되지만, <strong>접근 가능 여부는 컴파일 타임에 결정</strong>됨</li>
<li>자식 클래스 전용 함수를 사용하려면 자식 타입 포인터를 사용해야 함</li>
</ul>
<h3 id="🔸-pure-virtual-function-순수-가상-함수">🔸 Pure Virtual Function (순수 가상 함수)</h3>
<p><strong>순수 가상 함수 선언</strong></p>
<pre><code class="language-cpp">class Animal
{
public:
    virtual void speak() = 0;  // ✅ 순수 가상 함수
    // = 0 은 &quot;구현이 없음&quot;을 의미
};</code></pre>
<p><strong>Abstract Class (추상 클래스)</strong></p>
<ul>
<li><strong>순수 가상 함수가 하나라도 있는 클래스</strong>를 추상 클래스라고 함</li>
<li>추상 클래스는 <strong>객체를 생성할 수 없음</strong></li>
<li>추상 클래스를 상속받은 클래스는 <strong>모든 순수 가상 함수를 구현해야 객체 생성 가능</strong></li>
</ul>
<pre><code class="language-cpp">class Animal  // Abstract class
{
public:
    virtual void speak() = 0;  // 순수 가상 함수
    virtual void eat() { std::cout &lt;&lt; &quot;Eating&quot; &lt;&lt; std::endl; }  // 일반 가상 함수도 가능
    virtual ~Animal() = default;
};

class Cat : public Animal
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }  // ✅ 구현 필수
};

class Dog : public Animal
{
    // ❌ speak()를 구현하지 않음 → Dog도 추상 클래스가 됨
};

int main()
{
    Animal animal;  // ❌ 컴파일 에러! 추상 클래스는 객체 생성 불가
    Cat kitty;      // ✅ OK - speak()를 구현했으므로
    Dog puppy;      // ❌ 컴파일 에러! Dog도 추상 클래스

    Animal* ptr = new Cat();  // ✅ OK - 포인터는 가능
    ptr-&gt;speak();  // &quot;Meow~&quot;
    delete ptr;

    return 0;
}</code></pre>
<h3 id="🔸-interface-인터페이스">🔸 Interface (인터페이스)</h3>
<p><strong>인터페이스 정의</strong></p>
<ul>
<li><strong>순수 가상 함수만으로 이루어진 클래스</strong></li>
<li>구현(implementation)과 멤버 변수가 없음</li>
<li>다른 언어(Java, C#)의 interface와 동일한 개념</li>
</ul>
<pre><code class="language-cpp">// ✅ 좋은 인터페이스 설계
class IDrawable  // I 접두사는 인터페이스 표시 (관례)
{
public:
    virtual void draw() = 0;
    virtual void setColor(int r, int g, int b) = 0;
    virtual ~IDrawable() = default;  // 가상 소멸자는 필수

    // 멤버 변수 없음
    // 구현된 함수 없음
};

class Circle : public IDrawable
{
public:
    void draw() override { /* 원 그리기 구현 */ }
    void setColor(int r, int g, int b) override { /* 색상 설정 구현 */ }

private:
    double radius;
    int red, green, blue;
};</code></pre>
<p><strong>인터페이스 설계 원칙</strong></p>
<ol>
<li><p><strong>순수 가상 함수만 포함</strong></p>
<ul>
<li>구현 코드 없음</li>
<li>멤버 변수 없음</li>
</ul>
</li>
<li><p><strong>가상 소멸자 필수</strong></p>
<pre><code class="language-cpp">virtual ~IDrawable() = default;</code></pre>
</li>
<li><p><strong>복사/이동 방지 (권장)</strong></p>
<pre><code class="language-cpp">class IDrawable
{
public:
    virtual void draw() = 0;
    virtual ~IDrawable() = default;

protected:
    IDrawable() = default;  // 생성자 protected
    IDrawable(const IDrawable&amp;) = delete;  // 복사 금지
    IDrawable&amp; operator=(const IDrawable&amp;) = delete;
};</code></pre>
</li>
</ol>
<p><strong>인터페이스 사용의 장점</strong></p>
<ul>
<li><strong>유지보수성 향상</strong>: 구현과 인터페이스 분리</li>
<li><strong>의존성 감소</strong>: 구체적인 클래스 대신 인터페이스에 의존</li>
<li><strong>테스트 용이</strong>: Mock 객체 생성 쉬움</li>
<li><strong>플러그인 아키텍처</strong>: 런타임에 구현체 교체 가능</li>
</ul>
<pre><code class="language-cpp">// 나쁜 설계: 구체 클래스에 의존
void processImage(JPEGImage&amp; img) { /* ... */ }

// 좋은 설계: 인터페이스에 의존
void processImage(IImage&amp; img) { /* ... */ }
// JPEG, PNG, BMP 등 다양한 구현체 사용 가능</code></pre>
<p><strong>실전 예제: 다중 인터페이스 구현</strong></p>
<pre><code class="language-cpp">class ISerializable
{
public:
    virtual std::string serialize() = 0;
    virtual void deserialize(const std::string&amp; data) = 0;
    virtual ~ISerializable() = default;
};

class ILoggable
{
public:
    virtual void log() = 0;
    virtual ~ILoggable() = default;
};

class User : public ISerializable, public ILoggable
{
public:
    std::string serialize() override { /* JSON 변환 */ }
    void deserialize(const std::string&amp; data) override { /* JSON 파싱 */ }
    void log() override { /* 로그 출력 */ }

private:
    std::string name;
    int age;
};</code></pre>
<h3 id="🔸-핵심-정리">🔸 핵심 정리</h3>
<p><strong>상속 사용 가이드라인</strong></p>
<ol>
<li><strong>public 상속만 사용</strong> (99%의 경우)</li>
<li><strong>Base 클래스 소멸자는 virtual public 또는 protected</strong></li>
<li><strong>다형성이 필요하면 virtual 함수 사용</strong></li>
<li><strong>인터페이스는 순수 가상 함수만으로 구성</strong></li>
<li><strong>override 키워드로 의도 명확히 표현</strong></li>
</ol>
<pre><code class="language-cpp">// ✅ 올바른 상속 구조
class IAnimal  // 인터페이스
{
public:
    virtual void speak() = 0;
    virtual ~IAnimal() = default;
};

class Animal : public IAnimal  // 추상 클래스
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Animal&quot; &lt;&lt; std::endl; }
    virtual ~Animal() = default;

protected:
    int age;  // 공통 데이터
};

class Cat final : public Animal  // 구체 클래스 (final로 더 이상 상속 금지)
{
public:
    void speak() override { std::cout &lt;&lt; &quot;Meow~&quot; &lt;&lt; std::endl; }
};</code></pre>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/derived_class">C++ Inheritance</a>
<a href="https://en.cppreference.com/w/cpp/language/virtual">C++ Virtual Functions</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] Operator Overloading & Class Keywords]]></title>
            <link>https://velog.io/@sehee-jj/C-Operator-Overloading-Keywords</link>
            <guid>https://velog.io/@sehee-jj/C-Operator-Overloading-Keywords</guid>
            <pubDate>Sat, 03 Jan 2026 10:08:45 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-함수연산자-오버로딩과-클래스-주요-키워드">📚 C++ 함수/연산자 오버로딩과 클래스 주요 키워드</h2>
<h3 id="🔸-functionoperator-overloading">🔸 Function/Operator Overloading</h3>
<p><strong>Function Overloading (함수 오버로딩)</strong></p>
<ul>
<li>같은 이름의 함수를 여러 개 정의</li>
<li>매개변수의 타입이나 개수가 달라야 함</li>
<li><strong>Name Mangling</strong>: 컴파일러가 함수 이름에 타입 정보를 추가하여 구분</li>
<li><strong>Static Polymorphism</strong>: 컴파일 타임에 어떤 함수를 호출할지 결정</li>
</ul>
<pre><code class="language-cpp">void print(int x) { std::cout &lt;&lt; &quot;int: &quot; &lt;&lt; x &lt;&lt; std::endl; }
void print(double x) { std::cout &lt;&lt; &quot;double: &quot; &lt;&lt; x &lt;&lt; std::endl; }
void print(std::string x) { std::cout &lt;&lt; &quot;string: &quot; &lt;&lt; x &lt;&lt; std::endl; }

int main()
{
    print(10);        // print(int) 호출
    print(3.14);      // print(double) 호출
    print(&quot;hello&quot;);   // print(std::string) 호출
    return 0;
}</code></pre>
<p><strong>Operator Overloading (연산자 오버로딩)</strong></p>
<pre><code class="language-cpp">struct ComplexNum
{
    double real;
    double imag;

    ComplexNum(double r, double i) : real{r}, imag{i} {}

    void print() const
    {
        std::cout &lt;&lt; real &lt;&lt; &quot; + &quot; &lt;&lt; imag &lt;&lt; &quot;i&quot; &lt;&lt; std::endl;
    }
};

// + 연산자 오버로딩 (전역 함수로 구현)
ComplexNum operator+(const ComplexNum&amp; lhs, const ComplexNum&amp; rhs)
{
    return ComplexNum{lhs.real + rhs.real, lhs.imag + rhs.imag};
}

int main()
{
    ComplexNum c1{1, 1};
    ComplexNum c2{1, 2};

    ComplexNum c3 = c1 + c2;  // operator+(c1, c2) 호출
    c3.print();  // 2 + 3i

    return 0;
}</code></pre>
<p><strong>멤버 함수 vs 전역 함수</strong></p>
<pre><code class="language-cpp">// 멤버 함수로 구현
struct ComplexNum
{
    ComplexNum operator+(const ComplexNum&amp; rhs) const
    {
        return ComplexNum{real + rhs.real, imag + rhs.imag};
    }
};
// c1 + c2 → c1.operator+(c2)

// 전역 함수로 구현
ComplexNum operator+(const ComplexNum&amp; lhs, const ComplexNum&amp; rhs)
{
    return ComplexNum{lhs.real + rhs.real, lhs.imag + rhs.imag};
}
// c1 + c2 → operator+(c1, c2)</code></pre>
<p><strong>전역 함수가 필요한 경우:</strong></p>
<ul>
<li>왼쪽 피연산자가 다른 타입일 때 (예: <code>2 * complex</code>)</li>
<li>대칭적인 연산을 구현할 때</li>
</ul>
<p><strong>private 멤버 접근 문제:</strong></p>
<ul>
<li>전역 함수는 클래스의 private 멤버에 접근할 수 없음</li>
<li><code>friend</code> 키워드로 접근을 허용할 수 있지만, <strong>캡슐화를 깨뜨리므로 권장하지 않음</strong></li>
<li>대신 <strong>public getter 함수</strong>를 제공하거나 멤버를 <code>public</code>으로 만드는 것이 좋음 (간단한 구조체의 경우)</li>
</ul>
<pre><code class="language-cpp">// ❌ friend 사용 (캡슐화 위반)
class ComplexNum {
    double real;
    double imag;
    friend ComplexNum operator+(const ComplexNum&amp;, const ComplexNum&amp;);
};

// ✅ public 멤버 또는 getter 사용
struct ComplexNum {
    double real;  // 간단한 데이터 구조는 public으로
    double imag;
};
// 또는
class ComplexNum {
    double real;
    double imag;
public:
    double getReal() const { return real; }
    double getImag() const { return imag; }
};</code></pre>
<h3 id="🔸-class-관련-키워드">🔸 Class 관련 키워드</h3>
<p><strong>const (멤버 함수)</strong></p>
<pre><code class="language-cpp">class Cat
{
    std::string mName;
    int mAge;

public:
    // const 멤버 함수: 객체의 상태를 변경하지 않음
    void print() const
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; &quot; &lt;&lt; mAge &lt;&lt; std::endl;
        // mAge = 10;  // ❌ 컴파일 에러!
    }

    std::string getName() const { return mName; }  // ✅ OK

    void setAge(int age) { mAge = age; }  // non-const 함수
};

int main()
{
    const Cat kitty{&quot;kitty&quot;, 1};
    kitty.print();      // ✅ OK (const 함수)
    kitty.getName();    // ✅ OK (const 함수)
    kitty.setAge(2);    // ❌ 컴파일 에러! (non-const 함수)
    return 0;
}</code></pre>
<p><strong>mutable (가변 멤버 변수)</strong></p>
<ul>
<li><code>const</code> 멤버 함수 내에서도 수정 가능한 멤버 변수</li>
<li>주로 캐싱, 통계, 디버깅 정보 등에 사용</li>
<li>남용하면 <code>const</code>의 의미가 퇴색되므로 신중하게 사용</li>
</ul>
<pre><code class="language-cpp">class Cat
{
    std::string mName;
    mutable int mCallCount;  // mutable 멤버

public:
    Cat(std::string name) : mName{std::move(name)}, mCallCount{0} {}

    std::string getName() const
    {
        mCallCount++;  // ✅ const 함수지만 수정 가능
        return mName;
    }

    int getCallCount() const { return mCallCount; }
};

int main()
{
    const Cat kitty{&quot;kitty&quot;};
    kitty.getName();
    kitty.getName();
    std::cout &lt;&lt; kitty.getCallCount();  // 2
    return 0;
}</code></pre>
<p><strong>explicit (명시적 변환)</strong></p>
<ul>
<li>암묵적 타입 변환(Implicit Conversion)을 방지</li>
<li>주로 단일 매개변수 생성자에 사용</li>
</ul>
<p><strong>Implicit Conversion 문제 상황</strong></p>
<pre><code class="language-cpp">class Cat
{
    int mAge;

public:
    Cat(int age) : mAge{age} {}  // 단일 매개변수 생성자
};

void func(Cat cat) { /* ... */ }

int main()
{
    Cat kitty = 3;      // ✅ Cat(3)으로 암묵적 변환
    func(5);            // ✅ Cat(5)로 암묵적 변환
    // 의도하지 않은 변환이 발생할 수 있음!
    return 0;
}</code></pre>
<p><strong>explicit으로 해결</strong></p>
<pre><code class="language-cpp">class Cat
{
    int mAge;

public:
    explicit Cat(int age) : mAge{age} {}  // explicit 키워드
};

int main()
{
    Cat kitty = 3;      // ❌ 컴파일 에러!
    Cat kitty{3};       // ✅ OK (명시적 생성)
    Cat kitty(3);       // ✅ OK (명시적 생성)

    func(5);            // ❌ 컴파일 에러!
    func(Cat{5});       // ✅ OK (명시적 생성)

    return 0;
}</code></pre>
<p><strong>explicit 사용 가이드라인</strong></p>
<ul>
<li><strong>단일 매개변수 생성자는 거의 항상 <code>explicit</code>을 붙이는 것이 좋음</strong></li>
<li>의도적인 암묵적 변환이 필요한 경우만 생략 (예: wrapper 클래스)</li>
<li>C++11부터는 여러 매개변수 생성자에도 사용 가능</li>
</ul>
<pre><code class="language-cpp">class Vector
{
public:
    explicit Vector(int size) {}           // ✅ explicit 권장
    Vector(int x, int y, int z) {}         // 여러 매개변수는 선택적
    explicit Vector(std::vector&lt;int&gt; v) {} // ✅ explicit 권장
};</code></pre>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/operators">C++ Operator Overloading</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] Constructor, Copy/Move Semantics]]></title>
            <link>https://velog.io/@sehee-jj/C-Constructor-CopyMove-Semantics</link>
            <guid>https://velog.io/@sehee-jj/C-Constructor-CopyMove-Semantics</guid>
            <pubDate>Sat, 03 Jan 2026 10:07:18 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-생성자와-복사이동-semantics">📚 C++ 생성자와 복사/이동 semantics</h2>
<h3 id="🔸-member-initializer-list-멤버-초기화-리스트">🔸 Member Initializer List (멤버 초기화 리스트)</h3>
<p><strong>문제가 되는 코드</strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    Cat() { mAge = 1; }
    Cat(int age) { mAge = age; }
private:
    int mAge;
};

class Zoo
{
public:
    Zoo(int kittyAge) { mKitty = Cat(kittyAge); }  // ❌ 비효율적!
private:
    Cat mKitty;
};

int main()
{
    Zoo cppZoo(4);
    return 0;
}</code></pre>
<p><strong>문제점: 불필요한 임시 객체 생성</strong></p>
<ol>
<li><code>Zoo</code> 생성자가 실행되기 전에 <code>mKitty</code>의 기본 생성자 <code>Cat()</code>가 먼저 호출됨</li>
<li>생성자 본문에서 <code>Cat(kittyAge)</code>로 임시 객체를 생성</li>
<li>임시 객체를 <code>mKitty</code>에 대입 (복사 또는 이동)</li>
<li>임시 객체 소멸</li>
</ol>
<p>→ <strong>총 2번의 생성자 호출 + 1번의 대입 연산 발생</strong></p>
<p><strong>Member Initializer List로 해결</strong></p>
<pre><code class="language-cpp">class Zoo
{
public:
    Zoo(int kittyAge) : mKitty{kittyAge} {}  // ✅ 효율적!
private:
    Cat mKitty;
};</code></pre>
<p><strong>장점:</strong></p>
<ul>
<li><code>mKitty</code>를 생성과 동시에 초기화</li>
<li>불필요한 기본 생성자 호출 제거</li>
<li>임시 객체 생성 없음</li>
<li><strong>단 1번의 생성자 호출만 발생</strong></li>
</ul>
<p><strong>Member Initializer List 사용 규칙</strong></p>
<ul>
<li><code>()</code> 대신 <code>{}</code>를 사용하면 더 안전 (narrowing conversion 방지)</li>
<li>멤버 변수 선언 순서대로 초기화됨 (초기화 리스트 순서와 무관)</li>
<li><code>const</code> 멤버 변수와 참조 멤버 변수는 반드시 초기화 리스트를 사용해야 함</li>
</ul>
<pre><code class="language-cpp">class Example
{
public:
    Example(int a, int b, int c) 
        : mA{a}           // 멤버 변수
        , mB{b}           // 콤마로 구분
        , mC{c} {}        // 마지막도 콤마 가능

private:
    int mA;
    int mB;
    int mC;
};</code></pre>
<h3 id="🔸-copymove-constructor--assignment">🔸 Copy/Move Constructor &amp; Assignment</h3>
<p><strong>기본 예제 코드</strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    Cat() = default;  // 기본 생성자는 컴파일러가 자동 생성

    Cat(std::string name, int age) 
        : mName{std::move(name)}
        , mAge{age}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; constructor&quot; &lt;&lt; std::endl;
    }

    ~Cat()
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; destructor&quot; &lt;&lt; std::endl;
    }

    // Copy Constructor (복사 생성자)
    Cat(const Cat&amp; other) 
        : mName{other.mName}
        , mAge{other.mAge}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; copy constructor&quot; &lt;&lt; std::endl;
    }

    // Move Constructor (이동 생성자)
    Cat(Cat&amp;&amp; other) noexcept
        : mName{std::move(other.mName)}
        , mAge{other.mAge}
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; move constructor&quot; &lt;&lt; std::endl;
    }

    // Copy Assignment (복사 대입 연산자)
    Cat&amp; operator=(const Cat&amp; other)
    {
        if (&amp;other == this) return *this;  // Self-assignment 체크

        mName = other.mName;
        mAge = other.mAge;
        std::cout &lt;&lt; mName &lt;&lt; &quot; copy assignment&quot; &lt;&lt; std::endl;
        return *this;
    }

    // Move Assignment (이동 대입 연산자)
    Cat&amp; operator=(Cat&amp;&amp; other) noexcept
    {
        if (&amp;other == this) return *this;  // Self-assignment 체크

        mName = std::move(other.mName);
        mAge = other.mAge;
        std::cout &lt;&lt; mName &lt;&lt; &quot; move assignment&quot; &lt;&lt; std::endl;
        return *this;
    }

    void print() const
    {
        std::cout &lt;&lt; mName &lt;&lt; &quot; &quot; &lt;&lt; mAge &lt;&lt; std::endl;
    }

private:
    std::string mName;
    int mAge;
};</code></pre>
<p><strong>사용 예제</strong></p>
<pre><code class="language-cpp">int main()
{
    Cat kitty{&quot;kitty&quot;, 1};              // 일반 생성자
    Cat kitty2{kitty};                  // 복사 생성자
    Cat kitty3{std::move(kitty)};       // 이동 생성자

    Cat bori{&quot;bori&quot;, 1};
    Cat nabi{&quot;nabi&quot;, 2};

    //bori = nabi;                        // 복사 대입 연산자
    bori = std::move(nabi);             // 이동 대입 연산자

    bori.print();  // nabi 2 (nabi의 데이터를 가져옴)
    nabi.print();  // (empty) 2 (mName이 이동되어 비어있음)

    return 0;
}</code></pre>
<ul>
<li>일반/복사/이동 생성자
  <img src="https://velog.velcdn.com/images/sehee-jj/post/b7fee8dd-ce83-40e2-ab5a-2bc903bd0f70/image.png" alt=""></li>
<li>복사/이동 대입 연산자
  <img src="https://velog.velcdn.com/images/sehee-jj/post/4d8a4803-abb1-47c0-aa94-a85ca234e90b/image.png" alt=""></li>
</ul>
<h3 id="🔸-self-assignment-문제와-해결">🔸 Self-Assignment 문제와 해결</h3>
<p><strong>Self-Assignment란?</strong></p>
<pre><code class="language-cpp">Cat kitty{&quot;kitty&quot;, 1};
kitty = kitty;              // 자기 자신을 대입
kitty = std::move(kitty);   // 자기 자신을 이동</code></pre>
<p><strong>문제 상황</strong></p>
<ul>
<li>기본 타입(<code>int</code>, <code>string</code> 등) 멤버만 있으면 큰 문제 없음</li>
<li><strong>포인터 멤버 변수</strong>가 있으면 심각한 문제 발생 가능</li>
</ul>
<pre><code class="language-cpp">class Cat
{
    std::string* mName;  // 포인터 멤버

public:
    Cat&amp; operator=(const Cat&amp; other)
    {
        delete mName;              // 1. 자신의 메모리 해제
        mName = new std::string(*other.mName);  // 2. other도 같은 객체면?
        // → 이미 해제된 메모리를 읽으려고 시도! (Undefined Behavior)
        return *this;
    }
};</code></pre>
<p><strong>해결 방법: Self-Assignment 체크</strong></p>
<pre><code class="language-cpp">Cat&amp; operator=(const Cat&amp; other)
{
    if (&amp;other == this) return *this;  // ✅ 자기 자신이면 바로 리턴

    mName = other.mName;
    mAge = other.mAge;
    std::cout &lt;&lt; mName &lt;&lt; &quot; copy assignment&quot; &lt;&lt; std::endl;
    return *this;
}

Cat&amp; operator=(Cat&amp;&amp; other) noexcept
{
    if (&amp;other == this) return *this;  // ✅ 이동에서도 체크 필요

    mName = std::move(other.mName);
    mAge = other.mAge;
    std::cout &lt;&lt; mName &lt;&lt; &quot; move assignment&quot; &lt;&lt; std::endl;
    return *this;
}</code></pre>
<h3 id="🔸-noexcept-키워드">🔸 noexcept 키워드</h3>
<p><strong>noexcept를 붙여야 하는 함수</strong></p>
<ol>
<li>Destructor (소멸자)</li>
<li>Move Constructor (이동 생성자)</li>
<li>Move Assignment (이동 대입 연산자)</li>
</ol>
<p><strong>이유:</strong></p>
<ul>
<li>위 세 함수는 <strong>새로운 리소스를 요청하지 않음</strong></li>
<li>따라서 메모리 할당 실패 등의 예외가 발생할 가능성이 없음</li>
<li><code>noexcept</code>를 명시하면 컴파일러가 더 효율적인 코드를 생성할 수 있음</li>
<li>STL 컨테이너가 move semantics를 안전하게 사용할 수 있음</li>
</ul>
<pre><code class="language-cpp">~Cat() noexcept {}  // 소멸자는 암묵적으로 noexcept이지만 명시 가능

Cat(Cat&amp;&amp; other) noexcept 
    : mName{std::move(other.mName)}
    , mAge{other.mAge} {}

Cat&amp; operator=(Cat&amp;&amp; other) noexcept
{
    if (&amp;other == this) return *this;
    mName = std::move(other.mName);
    mAge = other.mAge;
    return *this;
}</code></pre>
<p><strong>noexcept의 중요성</strong></p>
<pre><code class="language-cpp">std::vector&lt;Cat&gt; cats;
cats.push_back(Cat{&quot;kitty&quot;, 1});

// vector가 크기를 늘릴 때:
// - Move constructor가 noexcept면 → 이동 사용 (빠름)
// - noexcept가 없으면 → 복사 사용 (느림, 안전함)</code></pre>
<h3 id="🔸-특수-멤버-함수-제어">🔸 특수 멤버 함수 제어</h3>
<p><strong>함수 사용 금지: <code>= delete</code></strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    Cat(const Cat&amp; other) = delete;  // 복사 생성자 사용 금지
    Cat&amp; operator=(const Cat&amp; other) = delete;  // 복사 대입 사용 금지
};

int main()
{
    Cat kitty{&quot;kitty&quot;, 1};
    Cat kitty2{kitty};  // ❌ 컴파일 에러!
    return 0;
}</code></pre>
<p><strong>사용 사례:</strong></p>
<ul>
<li>복사하면 안 되는 리소스 관리 클래스 (파일 핸들, 뮤텍스 등)</li>
<li>싱글톤 패턴 구현</li>
<li>이동만 허용하고 복사는 금지하려는 경우</li>
</ul>
<p><strong>C++11 이전 방식</strong></p>
<pre><code class="language-cpp">class Cat
{
private:
    Cat(const Cat&amp; other);  // 선언만 하고 구현하지 않음
    Cat&amp; operator=(const Cat&amp; other);
    // private이므로 외부에서 호출 불가
    // 구현이 없으므로 클래스 내부에서도 링크 에러 발생
};</code></pre>
<p><strong>Rule of Zero/Three/Five</strong></p>
<ul>
<li><strong>Rule of Zero</strong>: 특수 멤버 함수를 하나도 정의하지 않음 (컴파일러에게 맡김)</li>
<li><strong>Rule of Three</strong>: 소멸자, 복사 생성자, 복사 대입 중 하나를 정의하면 나머지도 정의</li>
<li><strong>Rule of Five</strong>: 위 3개 + 이동 생성자 + 이동 대입 (C++11 이후)</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/constructor">C++ Constructors and Destructors</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] OOP - 클래스와 메모리]]></title>
            <link>https://velog.io/@sehee-jj/C-OOP-%EA%B8%B0%EC%B4%88-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EB%A9%94%EB%AA%A8%EB%A6%AC</link>
            <guid>https://velog.io/@sehee-jj/C-OOP-%EA%B8%B0%EC%B4%88-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EB%A9%94%EB%AA%A8%EB%A6%AC</guid>
            <pubDate>Fri, 02 Jan 2026 09:19:12 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-oop와-클래스-개념">📚 C++ OOP와 클래스 개념</h2>
<h3 id="🔸-oop---class-소개">🔸 OOP - Class 소개</h3>
<p><strong>기본 개념</strong></p>
<ul>
<li><strong>Class</strong> : 객체를 만들기 위한 정의, 설계도, 틀</li>
<li><strong>Object</strong> : 클래스로부터 생성된 각각의 실체, 인스턴스</li>
</ul>
<p><strong>OOP의 4가지 핵심 특징</strong></p>
<ol>
<li><p><strong>Abstraction (추상화)</strong></p>
<ul>
<li>일반화(Generalization)와 모델링</li>
<li>복잡한 현실 세계를 클래스로 단순화하여 표현</li>
</ul>
</li>
<li><p><strong>Encapsulation (캡슐화)</strong></p>
<ul>
<li><code>private</code> 접근 지정자를 통해 구현</li>
<li>데이터와 메서드를 하나로 묶고 외부로부터 보호</li>
</ul>
</li>
<li><p><strong>Inheritance (상속)</strong></p>
<ul>
<li>기존 클래스의 특성을 물려받아 재사용</li>
<li>코드의 재사용성과 확장성 향상</li>
</ul>
</li>
<li><p><strong>Polymorphism (다형성)</strong></p>
<ul>
<li><strong>Static Polymorphism</strong> : Function Overloading (컴파일 타임에 결정)</li>
<li><strong>Dynamic Polymorphism</strong> : Function Overriding (<code>virtual</code> 키워드 사용, 런타임에 결정)</li>
</ul>
</li>
</ol>
<h3 id="🔸-object-alignment-객체-메모리-정렬">🔸 Object Alignment (객체 메모리 정렬)</h3>
<p><strong>메모리 정렬 예제</strong></p>
<pre><code class="language-cpp">class Cat
{
public:
    void Speak();
private:
    double d8;    // 8 bytes
    int i4a;      // 4 bytes
    int i4b;      // 4 bytes
};</code></pre>
<p><strong>메모리 크기 변화</strong></p>
<ul>
<li><p>위 코드의 경우: <code>Cat</code> 객체 크기 = 16 bytes
<img src="https://velog.velcdn.com/images/sehee-jj/post/5a50c197-7dd7-4e6f-80fe-b9ebf6a15ea8/image.png" alt=""></p>
</li>
<li><p><code>d8</code>과 <code>i4a</code>의 선언 순서를 바꾸면: 24 bytes로 증가
<img src="https://velog.velcdn.com/images/sehee-jj/post/ee3bd08c-ace9-4a05-bd16-7b50a062e83c/image.png" alt=""></p>
</li>
</ul>
<p><strong>Padding이 발생하는 이유</strong></p>
<ul>
<li><strong>메모리 정렬 규칙(Alignment Rule)</strong> 때문에 발생</li>
<li>각 멤버 변수는 자신의 크기의 배수인 주소에서 시작해야 함<ul>
<li><code>int</code> (4 bytes) : 0, 4, 8, 12, 16, ... 주소에서 시작</li>
<li><code>double</code> (8 bytes) : 0, 8, 16, 24, 32, ... 주소에서 시작</li>
</ul>
</li>
<li>전체 객체 크기는 가장 큰 멤버 변수 크기의 배수여야 함</li>
<li>CPU가 정렬되지 않은 메모리에 접근할 때 성능이 저하되거나 오류가 발생할 수 있어 컴파일러가 자동으로 패딩을 삽입함</li>
</ul>
<p><strong>메모리 최적화 팁</strong></p>
<ul>
<li>구조체나 클래스 설계 시 멤버 변수를 크기 순으로 정렬하면 패딩을 최소화할 수 있음</li>
<li>큰 타입(double, long long)을 먼저, 작은 타입(char, bool)을 나중에 배치</li>
<li><code>#pragma pack</code> 지시자로 패딩을 제어할 수도 있지만, 성능 저하가 발생할 수 있으므로 주의 필요</li>
</ul>
<h3 id="🔸-alignas-키워드">🔸 alignas 키워드</h3>
<p><strong>False Sharing 문제</strong>
  <img src="https://velog.velcdn.com/images/sehee-jj/post/5713e802-7115-4710-8777-a85d7c667f9a/image.png" alt=""></p>
<ul>
<li><code>Cat arr[100]</code>과 같은 배열이 있을 때 발생 가능</li>
<li>캐시 라인(Cache Line)의 최소 단위는 64 bytes</li>
<li><code>Cat</code> 객체가 24 bytes인 경우, 하나의 객체가 두 개의 캐시 라인에 나뉘어 저장될 수 있음</li>
<li>이로 인해 false sharing이 발생하여 성능 저하 초래</li>
</ul>
<p><strong>alignas를 통한 해결</strong></p>
<pre><code class="language-cpp">class alignas(32) Cat
{
    // ...
};</code></pre>
<ul>
<li>객체를 32 bytes 단위로 정렬하여 false sharing 방지</li>
<li>캐시 효율성 향상</li>
</ul>
<h3 id="🔸-static-in-class">🔸 Static in Class</h3>
<p><strong>1. Static Member Function (정적 멤버 함수)</strong></p>
<ul>
<li>객체를 생성하지 않고도 호출 가능</li>
<li>클래스 이름으로 직접 접근<pre><code class="language-cpp">Cat::StaticSpeak();</code></pre>
</li>
</ul>
<p><strong>2. Static Member Variable (정적 멤버 변수)</strong></p>
<ul>
<li>클래스 외부에서 명시적으로 초기화 필요<pre><code class="language-cpp">class Cat {
    static int count;
};
int Cat::count = 0;  // 클래스 외부에서 초기화</code></pre>
</li>
<li>Static 메모리 영역(데이터 세그먼트)에 할당됨</li>
<li>모든 객체가 공유하는 변수</li>
<li>프로그램 시작 시 초기화되고 프로그램 종료 시까지 유지됨</li>
</ul>
<p><strong>3. Static Variable in a Function (함수 내 정적 변수)</strong></p>
<ul>
<li>함수 내부에서 선언된 정적 변수<pre><code class="language-cpp">void func() {
    static int count = 0;  // 함수가 처음 호출될 때 초기화
    count++;
}</code></pre>
</li>
<li>함수가 처음 호출될 때 초기화됨</li>
<li>함수 호출이 끝나도 값이 유지됨</li>
</ul>
<p><strong>초기화 시점 비교</strong></p>
<ul>
<li><strong>Static member variable</strong>: 프로그램 시작 전 (전역 변수와 동일)</li>
<li><strong>Static variable in function</strong>: 함수가 처음 호출될 때 (지연 초기화)</li>
</ul>
<h3 id="🔸-this-키워드">🔸 this 키워드</h3>
<p><strong>this의 역할</strong></p>
<ul>
<li>현재 객체의 주소를 가리키는 포인터</li>
<li>멤버 함수 내에서 암묵적으로 사용됨</li>
</ul>
<p><strong>Static 멤버 함수의 제약</strong></p>
<ul>
<li>Static 멤버 함수는 특정 객체와 연결되어 있지 않음</li>
<li>따라서 <code>this</code> 포인터가 존재하지 않음</li>
<li>일반 멤버 변수나 멤버 함수를 사용할 수 없음<ul>
<li>이들은 <code>this</code>를 통해 호출되기 때문</li>
</ul>
</li>
</ul>
<p><strong>this 포인터 활용</strong></p>
<ul>
<li>멤버 변수와 매개변수 이름이 같을 때 구분<pre><code class="language-cpp">void SetName(string name) {
    this-&gt;name = name;  // 멤버 변수와 매개변수 구분
}</code></pre>
</li>
<li>메서드 체이닝 구현<pre><code class="language-cpp">Cat&amp; SetAge(int age) {
    this-&gt;age = age;
    return *this;  // 자기 자신을 반환
}</code></pre>
</li>
</ul>
<h3 id="🔸-inline-const-constexpr-정리">🔸 inline, const, constexpr 정리</h3>
<p><strong>inline 키워드</strong></p>
<ul>
<li>함수 호출 오버헤드를 제거하기 위한 키워드</li>
<li>컴파일러에게 함수 본문을 호출 지점에 직접 삽입하도록 요청</li>
<li>작고 자주 호출되는 함수에 사용하면 효과적</li>
<li>클래스 내부에서 정의된 멤버 함수는 자동으로 inline 처리됨</li>
<li>주의: 함수가 너무 크면 코드 크기가 증가할 수 있음 (컴파일러가 무시할 수 있음)<pre><code class="language-cpp">inline int Add(int a, int b) { return a + b; }
// 호출 시 → int result = a + b; 로 직접 삽입됨</code></pre>
</li>
</ul>
<p><strong>const 키워드 (멤버 함수)</strong></p>
<ul>
<li>멤버 함수가 객체의 상태(멤버 변수)를 변경하지 않음을 명시</li>
<li>const 객체는 const 멤버 함수만 호출할 수 있음</li>
<li>함수 선언 뒤에 const를 붙여 사용<pre><code class="language-cpp">class Cat {
  int age;
public:
  int GetAge() const { return age; }  // ✅ 읽기만 가능
  void SetAge(int a) { age = a; }     // ❌ const 객체는 호출 불가
};
</code></pre>
</li>
</ul>
<p>const Cat kitty;
kitty.GetAge();  // ✅ OK
kitty.SetAge(5); // ❌ 컴파일 에러</p>
<pre><code>
**constexpr 키워드**
- 컴파일 타임에 값이 결정되는 상수 표현식
- 런타임 성능 향상 (컴파일 시 계산 완료)
- 함수에 사용하면 컴파일 타임에 실행 가능
- C++11부터 도입, C++14/17에서 기능 확장
```cpp
constexpr int Square(int x) { return x * x; }

int arr[Square(5)];  // 컴파일 타임에 arr[25]로 결정됨

// static member variable과 함께 사용
class Math {
    static constexpr double PI = 3.14159;  // 컴파일 타임 상수
};</code></pre><p><strong>const vs constexpr 비교</strong></p>
<ul>
<li><code>const</code>: 런타임 또는 컴파일 타임 상수 (값 변경 불가)</li>
<li><code>constexpr</code>: 반드시 컴파일 타임 상수 (더 엄격함)<pre><code class="language-cpp">const int a = 10;           // 컴파일 타임 상수
const int b = GetValue();   // 런타임 상수 (함수 호출 결과)
</code></pre>
</li>
</ul>
<p>constexpr int c = 10;       // ✅ 컴파일 타임 상수
constexpr int d = GetValue(); // ❌ 컴파일 에러 (constexpr 함수여야 함)</p>
<p>```</p>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/classes">C++ Classes and Objects</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[25.10.28] :: 가디언 앤 시커 프로젝트 25]]></title>
            <link>https://velog.io/@sehee-jj/25.10.28-%EA%B0%80%EB%94%94%EC%96%B8-%EC%95%A4-%EC%8B%9C%EC%BB%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-25</link>
            <guid>https://velog.io/@sehee-jj/25.10.28-%EA%B0%80%EB%94%94%EC%96%B8-%EC%95%A4-%EC%8B%9C%EC%BB%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-25</guid>
            <pubDate>Mon, 27 Oct 2025 16:02:34 GMT</pubDate>
            <description><![CDATA[<h1 id="📝-개발일지---매뉴얼-ui-시스템-구현-인터페이스-기반-데이터-접근">📝 개발일지 - 매뉴얼 UI 시스템 구현 (인터페이스 기반 데이터 접근)</h1>
<h2 id="👨💻-오늘의-개발-작업">👨💻 오늘의 개발 작업</h2>
<p>오늘은 게임 내 매뉴얼(설명서) UI 시스템을 구축했음
플레이 도중 Tab키를 누르면 QuickManual이 나오며, 현재 캐릭터의 스킬 조작법을 볼 수 있는 기능임</p>
<p>RTS와 TPS 두 가지 컨트롤러 모드가 있고, TPS 모드에서는 4개의 서로 다른 캐릭터(Seeker 3개 + Guardian 1개)가 있는 상황에서, 각 모드와 캐릭터마다 다른 매뉴얼 이미지를 보여줘야 했음</p>
<p>처음에는 if문으로 모든 케이스를 처리하려 했지만, 인터페이스를 도입하여 확장 가능하고 유지보수가 쉬운 구조로 개선했음</p>
<hr>
<h2 id="💡-오늘의-5분-기록">💡 오늘의 5분 기록</h2>
<h3 id="1-데이터-테이블-구조-설계-및-이해">1. 데이터 테이블 구조 설계 및 이해</h3>
<p><strong>매뉴얼 데이터 구조체 정의</strong></p>
<pre><code class="language-cpp">USTRUCT(BlueprintType)
struct FManualImageRow : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Manual&quot;)
    uint8 PageIndex;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Manual&quot;)
    FText Title;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Manual&quot;)
    TSoftObjectPtr&lt;UTexture2D&gt; ManualImage;
};</code></pre>
<p><strong>데이터 테이블 구조 이해</strong></p>
<pre><code>RowName(키)    | PageIndex | Title      | ManualImage
---------------|-----------|------------|-------------
&quot;Guardian&quot;     | 1         | &quot;가디언&quot;    | Texture...
&quot;Ares&quot;         | 2         | &quot;아레스&quot;    | Texture...
&quot;Chan&quot;         | 3         | &quot;찬&quot;       | Texture...
&quot;Merci&quot;        | 4         | &quot;머시&quot;      | Texture...</code></pre><p><strong>데이터 접근 방법</strong></p>
<pre><code class="language-cpp">// RowName으로 검색 (O(1), 빠름)
FManualImageRow* RowData = ManualImageTable-&gt;FindRow&lt;FManualImageRow&gt;(
    FName(&quot;Guardian&quot;), 
    TEXT(&quot;InitImage&quot;)
);

if (RowData)
{
    UTexture2D* LoadedTexture = RowData-&gt;ManualImage.LoadSynchronous();
}</code></pre>
<h3 id="2-초기-접근-if문-분기-처리의-한계">2. 초기 접근: if문 분기 처리의 한계</h3>
<p><strong>문제점 발견</strong></p>
<pre><code class="language-cpp">// 각 컨트롤러/캐릭터마다 if문 필요
APlayerController* PC = GetOwningPlayer();
FName RowName;

if (ARTSController* RTSCtrl = Cast&lt;ARTSController&gt;(PC))
{
    RowName = FName(&quot;Guardian&quot;);
}
else if (ATPSController* TPSCtrl = Cast&lt;ATPSController&gt;(PC))
{
    APawn* Pawn = TPSCtrl-&gt;GetPawn();

    if (AAres* Ares = Cast&lt;AAres&gt;(Pawn))
        RowName = FName(&quot;Ares&quot;);
    else if (AChan* Chan = Cast&lt;AChan&gt;(Pawn))
        RowName = FName(&quot;Chan&quot;);
    else if (AMerci* Merci = Cast&lt;AMerci&gt;(Pawn))
        RowName = FName(&quot;Merci&quot;);
    else if (AGuardian* Guardian = Cast&lt;ADrakhar&gt;(Pawn))
        RowName = FName(&quot;Drakhar&quot;);
}</code></pre>
<p><strong>핵심 문제</strong></p>
<ul>
<li>새 캐릭터 추가 시 UI 코드 수정 필요</li>
<li>중복 코드 발생</li>
<li>유지보수성 저하</li>
</ul>
<h3 id="3-설계-개선-부모-클래스에-데이터-저장">3. 설계 개선: 부모 클래스에 데이터 저장</h3>
<p><strong>캐릭터 상속 구조 파악</strong></p>
<pre><code>TPS Controller
├── Seeker (부모)
│   ├── Ares
│   ├── Chan
│   └── Merci
└── Guardian (부모)</code></pre><p><strong>부모 클래스에 RowName 저장</strong></p>
<pre><code class="language-cpp">// Seeker.h
class ASeeker : public ACharacter
{
    GENERATED_BODY()

protected:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = &quot;Manual&quot;)
    FName ManualRowName;

public:
    FName GetManualRowName() const { return ManualRowName; }
};

// 자식 클래스 생성자에서 설정
AAres::AAres()
{
    ManualRowName = FName(&quot;Ares&quot;);
}

AChan::AChan()
{
    ManualRowName = FName(&quot;Chan&quot;);
}</code></pre>
<p><strong>Guardian도 동일하게 구현</strong></p>
<h3 id="4-최종-해결책-인터페이스-도입">4. 최종 해결책: 인터페이스 도입</h3>
<p><strong>인터페이스 정의</strong></p>
<pre><code class="language-cpp">// IManualDataInterface.h
UINTERFACE(MinimalAPI, Blueprintable)
class UGS_ManualDataInterface : public UInterface
{
    GENERATED_BODY()
};

class IGS_ManualDataInterface
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = &quot;Manual&quot;)
    FName GetManualRowName() const;
};</code></pre>
<p><strong>Seeker 클래스에 인터페이스 구현</strong></p>
<pre><code class="language-cpp">// Seeker.h
#include &quot;IGS_ManualDataInterface.h&quot;  // 헤더 include 필수

class ASeeker : public ACharacter, public IGS_ManualDataInterface
{
    GENERATED_BODY()

protected:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = &quot;Manual&quot;)
    FName ManualRowName;

public:
    // 인터페이스 함수 구현
    virtual FName GetManualRowName_Implementation() const override
    {
        return ManualRowName;
    }
};</code></pre>
<p><strong>Guardian 클래스도 동일하게 구현</strong></p>
<h3 id="5-ui-로직-대폭-간소화">5. UI 로직 대폭 간소화</h3>
<p><strong>통합된 단순 처리</strong></p>
<pre><code class="language-cpp">void UGS_QuickManualUI::InitImage()
{
    FName RowName;
    APlayerController* PC = GetOwningPlayer();

    // RTS 시점일 경우
    AGS_RTSController* RTSCtrl = Cast&lt;AGS_RTSController&gt;(PC);
    if (RTSCtrl)
    {
        RowName = FName(&quot;Guardian&quot;);
    }

    // TPS 시점일 경우 - 단 하나의 if문으로 처리!
    APawn* Pawn = PC-&gt;GetPawn();
    if (Pawn &amp;&amp; Pawn-&gt;Implements&lt;UGS_ManualDataInterface&gt;())  // U 접두사 사용
    {
        RowName = IGS_ManualDataInterface::Execute_GetManualRowName(Pawn);  // I 접두사 사용
    }

    // 데이터 테이블에서 이미지 로드
    if (!RowName.IsNone())
    {
        FManualImageRow* RowData = ManualImageTable-&gt;FindRow&lt;FManualImageRow&gt;(
            RowName, 
            TEXT(&quot;InitImage&quot;)
        );

        if (RowData)
        {
            UTexture2D* LoadedTexture = RowData-&gt;ManualImage.LoadSynchronous();
            if (LoadedTexture)
            {
                KeyManualImage-&gt;SetBrushFromTexture(LoadedTexture);
            }
        }
    }
}</code></pre>
<h3 id="6-트러블슈팅---인터페이스-사용-시-컴파일-에러-⚠️">6. 트러블슈팅 - 인터페이스 사용 시 컴파일 에러 ⚠️</h3>
<p><strong>문제 발생</strong></p>
<pre><code>error: &#39;IGS_ManualDataInterface&#39;: no member &#39;StaticClass&#39;
note: see reference to function template instantiation 
&#39;bool UObject::Implements&lt;IGS_ManualDataInterface&gt;(void) const&#39;</code></pre><p><strong>원인 분석</strong>
언리얼 인터페이스는 두 개의 클래스가 생성됨:</p>
<ul>
<li><strong>U 접두사</strong> (UGS_ManualDataInterface): 리플렉션 시스템용, UINTERFACE</li>
<li><strong>I 접두사</strong> (IGS_ManualDataInterface): 실제 인터페이스, 함수 정의</li>
</ul>
<p><strong>해결 방법</strong></p>
<pre><code class="language-cpp">// 잘못된 사용
if (Pawn-&gt;Implements&lt;IGS_ManualDataInterface&gt;())  // ❌ I 접두사 사용 불가

// 올바른 사용
if (Pawn-&gt;Implements&lt;UGS_ManualDataInterface&gt;())  // ✅ U 접두사 사용
{
    // 함수 호출 시에는 I 접두사 사용
    FName Name = IGS_ManualDataInterface::Execute_GetManualRowName(Pawn);
}</code></pre>
<p><strong>규칙 정리</strong></p>
<ol>
<li><code>Implements&lt;&gt;</code> 체크: <strong>U 접두사</strong> 사용</li>
<li><code>Execute_</code> 함수 호출: <strong>I 접두사</strong> 사용</li>
<li>상속 시: <strong>I 접두사</strong> 사용 (<code>public IGS_ManualDataInterface</code>)</li>
<li>헤더 include: 상속 시 필수, 사용만 할 때는 cpp에서 include 가능</li>
</ol>
<h3 id="7-성과와-개선-효과">7. 성과와 개선 효과</h3>
<p><strong>코드 간소화</strong></p>
<pre><code class="language-cpp">// 기존: 모든 캐릭터마다 if문 (20+ 줄)
if (AAres* Ares = Cast&lt;AAres&gt;(Pawn))
    RowName = FName(&quot;Ares&quot;);
else if (AChan* Chan = Cast&lt;AChan&gt;(Pawn))
    RowName = FName(&quot;Chan&quot;);
else if (AMerci* Merci = Cast&lt;AMerci&gt;(Pawn))
    RowName = FName(&quot;Merci&quot;);
else if (AGuardian* Guardian = Cast&lt;AGuardian&gt;(Pawn))
    RowName = FName(&quot;Guardian&quot;);

// 새로운: 단 하나의 if문 (3줄)
if (Pawn &amp;&amp; Pawn-&gt;Implements&lt;UGS_ManualDataInterface&gt;())
{
    RowName = IGS_ManualDataInterface::Execute_GetManualRowName(Pawn);
}</code></pre>
<p><strong>개발자 경험 개선</strong></p>
<ul>
<li>✅ <strong>확장성</strong>: 새 캐릭터 추가 시 UI 코드 수정 불필요</li>
<li>✅ <strong>유지보수성</strong>: 각 캐릭터가 자신의 데이터를 관리</li>
<li>✅ <strong>일관성</strong>: 모든 캐릭터를 동일한 방식으로 처리</li>
<li>✅ <strong>타입 안전성</strong>: 인터페이스로 계약 보장</li>
</ul>
<p><strong>배운 교훈</strong></p>
<ul>
<li>데이터 테이블의 RowName은 고유 식별자로 FindRow()의 키 역할</li>
<li>인터페이스는 다형성을 제공하여 if문 분기를 제거</li>
<li>언리얼 인터페이스의 U/I 접두사 규칙 이해 필수</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] Resource Move]]></title>
            <link>https://velog.io/@sehee-jj/C-Resource-Move</link>
            <guid>https://velog.io/@sehee-jj/C-Resource-Move</guid>
            <pubDate>Sun, 19 Oct 2025 09:08:07 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-매개변수-전달과-복사-최적화">📚 C++ 매개변수 전달과 복사 최적화</h2>
<h3 id="🔸-pass-by-value-vs-pass-by-pointer-vs-pass-by-reference">🔸 Pass by Value vs Pass by Pointer vs Pass by Reference</h3>
<p><strong>세 가지 전달 방식의 특징</strong></p>
<ul>
<li><strong>Pass by Value</strong> : 값을 복사해서 전달함. 원본 데이터는 변경되지 않음</li>
<li><strong>Pass by Pointer</strong> : 메모리 주소를 전달함. 포인터 연산이 가능하고, nullptr 체크가 필요함</li>
<li><strong>Pass by Reference</strong> : 참조를 통해 전달함. 포인터처럼 동작하지만 더 안전하고 편리함</li>
</ul>
<p><strong>성능 비교</strong></p>
<ul>
<li>Pointer와 Reference는 어셈블리 수준에서 동일하게 동작함</li>
<li>큰 객체를 전달할 때는 복사 비용이 큰 Pass by Value보다 Reference나 Pointer를 사용하는 것이 효율적임</li>
<li>포인터가 꼭 필요한 상황(nullptr 체크, 포인터 연산 등)이 아니라면 Reference를 사용하는 것이 더 안전하고 편리함</li>
</ul>
<h3 id="🔸-l-value와-r-value">🔸 L-value와 R-value</h3>
<p><strong>L-value (Left value)</strong></p>
<ul>
<li>메모리 상에 위치가 있어서 여러 번 참조할 수 있는 값</li>
<li>변수명으로 식별 가능한 객체</li>
<li>주소 연산자(&amp;)를 사용할 수 있음</li>
<li>예시: <code>int x = 10;</code>에서 <code>x</code>는 L-value</li>
</ul>
<p><strong>R-value (Right value)</strong></p>
<ul>
<li>임시로 생성되어 표현식이 끝나면 사라지는 값</li>
<li>메모리 주소를 가질 수 없음</li>
<li>대입 연산자의 오른쪽에만 올 수 있음</li>
<li>예시: <code>int x = 10;</code>에서 <code>10</code>은 R-value, <code>x + 5</code>도 R-value</li>
</ul>
<h3 id="🔸-l-value-reference-vs-r-value-reference">🔸 L-value Reference vs R-value Reference</h3>
<p><strong>L-value Reference (<code>&amp;</code>)</strong></p>
<ul>
<li>L-value를 참조하기 위한 방식</li>
<li>기존 변수를 참조할 때 사용</li>
<li>선언: <code>string&amp; ref = str;</code></li>
</ul>
<p><strong>R-value Reference (<code>&amp;&amp;</code>)</strong></p>
<ul>
<li>R-value를 참조하기 위한 방식 (C++11부터 도입)</li>
<li>임시 객체나 이동 가능한 객체를 받을 때 사용</li>
<li>Move semantics를 구현하기 위해 사용됨</li>
<li>주로 함수 매개변수로 활용: <code>void func(string&amp;&amp; s)</code></li>
</ul>
<blockquote>
<p><strong>Move Semantics란?</strong></p>
<ul>
<li>객체의 자원(메모리 등)을 복사하지 않고 이동시키는 최적화 기법</li>
<li>불필요한 복사를 줄여 성능을 향상시킴</li>
<li>R-value reference를 통해 구현됨</li>
</ul>
</blockquote>
<h3 id="🔸-예제-코드-매개변수-전달-방식">🔸 예제 코드: 매개변수 전달 방식</h3>
<pre><code class="language-cpp">void StoreByValue(string s)
{ 
    string b = s;
}
void StoreByLRef(string&amp; s)
{ 
    string b = s; 
}
void StoreByRRef(string&amp;&amp; s)
{ 
    //string b = s;
    string b = std::move(s);
}
int main()
{
    string a = &quot;abc&quot;;
    StoreByValue(a);
    StoreByLRef(a);
    StoreByRRef(&quot;abc&quot;);

    return 0;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sehee-jj/post/fa3cdcc6-4bde-42ac-86eb-306f89bb5e1a/image.jpg" alt=""></p>
<h3 id="🔸-stdmove를-통한-복사-최적화">🔸 std::move를 통한 복사 최적화</h3>
<p><strong>std::move의 역할</strong></p>
<ul>
<li>L-value를 R-value로 캐스팅하여 move semantics를 활용할 수 있게 해줌</li>
<li>불필요한 복사를 제거하고 이동으로 대체하여 성능을 향상시킴</li>
<li>객체의 소유권을 이전할 때 사용함</li>
</ul>
<p><strong>예제 코드: std::move 활용</strong></p>
<pre><code class="language-cpp">class Cat
{
    public:
        void SetName(string name)
        { 
            mName = std::move(name);
        }
    private:
        string mName;
};

int main()
{
    Cat kitty;
    string s = &quot;kitty&quot;;
    kitty.SetName(s); // 1 copy
    kitty.SetName(&quot;nabi&quot;); // 0 copy
}</code></pre>
<p><strong>복사 횟수 분석</strong></p>
<ul>
<li><code>kitty.SetName(s)</code> : L-value 전달 → 매개변수로 1 copy, std::move로 이동 → 총 1 copy</li>
<li><code>kitty.SetName(&quot;nabi&quot;)</code> : R-value 전달 → 매개변수로 이동, std::move로 이동 → 총 0 copy</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sehee-jj/post/3b1c70c9-c0e8-40b6-aded-35657c3c1497/image.jpg" alt=""></p>
<h3 id="🔸-rvo-return-value-optimization">🔸 RVO (Return Value Optimization)</h3>
<p><strong>RVO란?</strong></p>
<ul>
<li>컴파일러가 함수의 반환 과정에서 발생하는 불필요한 복사를 제거하는 최적화 기법</li>
<li>C++17부터는 특정 조건에서 RVO가 의무화됨 (guaranteed copy elision)</li>
<li>반환 값을 임시 객체로 만들지 않고 직접 목적지에 생성함</li>
</ul>
<p><strong>예제 코드: RVO 적용</strong></p>
<pre><code class="language-cpp">string getString()
{
    string s = &quot;hello&quot;;
    return s;
}

int main()
{
    string a = getString();
    return 0;
}</code></pre>
<p><strong>동작 방식</strong></p>
<ul>
<li>반환할 때 1 copy가 이루어질 것 같지만, RVO의 개입으로 s는 만들어지지 않고 바로 a에 hello가 저장되어 0 copy가 가능함</li>
</ul>
<p><strong>RVO가 개입되지 않는 경우</strong></p>
<pre><code class="language-cpp">string getString(string s)
{
    if(조건)
    {
        s = &quot;hello&quot;;
    }
    return s;
}</code></pre>
<ul>
<li>s에 바로 hello를 대입해도 되는지 모르는 상황이므로 RVO 개입이 불가능함</li>
<li>그래도 0 copy로 이루어지는데, 이는 move constructor 덕분임 (이건 나중에 정리)</li>
</ul>
<blockquote>
<p><strong>NRVO (Named RVO)</strong></p>
<ul>
<li>이름이 있는 지역 변수를 반환할 때의 최적화</li>
<li>컴파일러가 보장하지 않으며, 조건에 따라 적용 여부가 달라짐</li>
<li>여러 반환 경로가 있거나 조건부 반환이 있으면 적용되지 않을 수 있음</li>
</ul>
</blockquote>
<h3 id="🔸-사용-가이드라인">🔸 사용 가이드라인</h3>
<p><strong>매개변수 전달 방식 선택</strong></p>
<ol>
<li><p><strong>Pass by Value</strong> </p>
<ul>
<li>작은 타입(int, char 등)을 전달할 때</li>
<li>복사본이 필요할 때</li>
</ul>
</li>
<li><p><strong>Pass by Const Reference (<code>const T&amp;</code>)</strong></p>
<ul>
<li>큰 객체를 읽기 전용으로 전달할 때</li>
<li>가장 일반적으로 권장되는 방식</li>
</ul>
</li>
<li><p><strong>Pass by Reference (<code>T&amp;</code>)</strong></p>
<ul>
<li>함수 내에서 원본을 수정해야 할 때</li>
</ul>
</li>
<li><p><strong>Pass by R-value Reference (<code>T&amp;&amp;</code>)</strong></p>
<ul>
<li>Move semantics를 활용할 때</li>
<li>임시 객체를 효율적으로 처리할 때</li>
<li>Perfect forwarding을 구현할 때</li>
</ul>
</li>
<li><p><strong>Pass by Pointer (<code>T*</code>)</strong></p>
<ul>
<li>nullptr 가능성이 있을 때</li>
<li>포인터 연산이 필요할 때</li>
<li>C 라이브러리와 호환이 필요할 때</li>
</ul>
</li>
</ol>
<p><strong>최적화 활용</strong></p>
<ul>
<li>멤버 변수에 값을 저장할 때는 <code>std::move()</code>를 사용하여 불필요한 복사를 줄일 수 있음</li>
<li>함수 반환 시에는 RVO를 믿고 단순하게 값을 반환하는 것이 좋음 (명시적으로 std::move를 사용하면 오히려 RVO를 방해할 수 있음)</li>
<li>복잡한 조건부 반환이 있는 경우에도 컴파일러가 move constructor를 활용하여 최적화를 수행함</li>
</ul>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://en.cppreference.com/w/cpp/language/reference">C++ Reference와 Move Semantics</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] Compile Process]]></title>
            <link>https://velog.io/@sehee-jj/C-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B3%BC%EC%A0%95%EA%B3%BC-Linkage</link>
            <guid>https://velog.io/@sehee-jj/C-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B3%BC%EC%A0%95%EA%B3%BC-Linkage</guid>
            <pubDate>Mon, 29 Sep 2025 09:16:42 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-컴파일-과정">📚 C++ 컴파일 과정</h2>
<h3 id="🔸-컴파일-과정">🔸 컴파일 과정</h3>
<ul>
<li><strong>전처리기(Pre-processor)</strong> : #include, #define 같은 부분을 처리함</li>
<li><strong>컴파일러(Compiler)</strong> : 코드 파일을 번역해 obj파일(translation unit)로 생성함</li>
<li><strong>링커(Linker)</strong> : 각각의 translation unit을 연결해 실행파일을 생성함</li>
</ul>
<h3 id="🔸-링키지-과정-linkage-process">🔸 링키지 과정 (Linkage Process)</h3>
<ul>
<li><p><strong>extern 키워드</strong></p>
<ul>
<li>해당 식별자의 정의(심볼)가 현재 번역 단위 파일 바깥쪽에 있다고 링커에게 알려줌</li>
<li>전역 함수, 변수는 기본적으로 extern 키워드가 붙었다고 간주됨</li>
</ul>
</li>
<li><p><strong>static 키워드</strong></p>
<ul>
<li>자신의 translation unit 안에서만 접근이 가능하게 만드는 키워드임</li>
<li>internal linkage 방법으로 unnamed namespace를 더 권장하는 듯</li>
</ul>
</li>
</ul>
<h3 id="🔸-c와의-호환성">🔸 C와의 호환성</h3>
<ul>
<li><p><strong>extern &quot;C&quot; 키워드</strong></p>
<ul>
<li>C++ 소스에서 선언한 전역변수나 함수를 C에서 사용해야 할 경우 사용함</li>
<li>이 키워드를 붙이면 네임 맹글링을 진행하지 않음</li>
<li>C 인터페이스를 가진 심볼로 생성됨 (함수 오버로딩도 불가능해짐)</li>
</ul>
<blockquote>
<p><strong>네임 맹글링(Name Mangling)</strong></p>
</blockquote>
<ul>
<li>C++에서 지원하는 함수 오버로딩을 구현하기 위해 사용되는 심볼 작명 방식임</li>
<li>컴파일 시 함수 이름을 고유하게 변경하여 같은 이름의 다른 함수들을 구분함</li>
</ul>
</li>
</ul>
<h3 id="🔸-어셈블리-코드와-최적화">🔸 어셈블리 코드와 최적화</h3>
<p><strong>성능에 대한 오해</strong></p>
<ul>
<li>multiply 연산보다 shift bit 연산이 더 빠르다고 알려져 있지만, 실제 어셈블리어로 변환해보면 똑같음 (최신 컴파일러에서는 최적화를 통해 비슷한 성능을 보이는 경우가 많음)</li>
<li>어셈블리어가 더 짧다고 더 빠른 것은 아님</li>
<li>if/else문과 switch문도 똑같음</li>
<li>이런 이유들로 가독성이 좋은 코드를 선택하는 것이 좋음</li>
</ul>
<h3 id="🔸-디버깅과-최적화">🔸 디버깅과 최적화</h3>
<p><strong>디버그 옵션</strong></p>
<ul>
<li><code>-g</code> 옵션 : debug info가 포함됨</li>
<li>debug info는 binary(machine code)와 cpp source를 연결해주는 디버깅용 메타 데이터임(소스 코드 줄 번호, 변수명, 타입 정보 등 포함)</li>
<li><code>-o</code> 옵션 : optimization을 진행함</li>
</ul>
<h3 id="🔸-라이브러리-종류">🔸 라이브러리 종류</h3>
<p><strong>라이브러리 유형</strong></p>
<ul>
<li><strong>Header only</strong> : 헤더 파일만으로 구성됨</li>
<li><strong>Static library</strong> : 윈도우는 .lib / 리눅스는 .a 파일</li>
<li><strong>Dynamic library</strong> : 윈도우는 .dll / 리눅스는 .so 파일</li>
</ul>
<h4 id="🔹-static-library">🔹 Static Library</h4>
<p><strong>정의와 생성</strong></p>
<ul>
<li>오브젝트 파일을 모아서 하나의 아카이브 파일을 만듦</li>
<li>생성법: <code>ar -rs libcat.a cat.o</code> (cat.h에 대한 라이브러리 생성 시)</li>
</ul>
<p><strong>사용법</strong></p>
<ul>
<li><code>g++ main.cpp -lcat</code>으로 사용하지만 이렇게 하면 libcat.a파일이 표준 라이브러리 경로에 없어서 에러 발생함</li>
<li>해결법: <code>-L</code> 옵션을 붙여야 함 (특정 위치에서 libcat.a파일을 찾아서 적용)</li>
<li><code>-L.</code>이면 현재 디렉토리 위치를 의미함</li>
</ul>
<h4 id="🔹-dynamic-library-shared-library">🔹 Dynamic Library (Shared Library)</h4>
<p><strong>바인딩 타이밍</strong></p>
<ul>
<li><strong>Load time dynamic library</strong> : 로드 타임에 라이브러리가 바인딩됨</li>
<li><strong>Run time dynamic library</strong> : 런타임에 라이브러리가 바인딩됨</li>
<li>Static은 exe파일이 만들어질 때 바인딩됨</li>
<li>Run time은 특정 조건에서만 사용하고, 보통은 Load time을 씀</li>
</ul>
<p><strong>Load Time Dynamic Library 생성</strong></p>
<pre><code class="language-bash">g++ foo.cpp -fPIC -c
g++ -shared foo.o -o libfoo.so</code></pre>
<p><strong>-fPIC 옵션의 필요성</strong></p>
<ul>
<li>shared library가 여러 어플리케이션, 프로세스에서 실행되어야 함</li>
<li>-fPIC 옵션을 주지 않으면 absolute base address가 되어 서로 다른 앱에서 주소를 지정할 수 없음</li>
<li>-fPIC 옵션을 주면 relative base address를 가져서 각각의 어플에서 로드되고 호출될 수 있음</li>
</ul>
<p><strong>Run Time Dynamic Library 사용</strong>
런타임에 동적으로 라이브러리를 로드하고 함수를 호출하는 코드를 작성할 수 있음. 이를 위해서는 <code>&lt;dlfcn.h&gt;</code> 헤더를 포함하고 <code>dlopen</code>, <code>dlsym</code>, <code>dlclose</code> 함수들을 사용하여 라이브러리를 동적으로 관리할 수 있음</p>
<pre><code class="language-cpp">#include &lt;dlfcn.h&gt;

int main()
{
    void* handle = dlopen(&quot;./libfoo.so&quot;, RTLD_LAZY);
    if(!handle)
    {
        //handle 없음 출력
    }
    void (*fooPtr)();
    fooPtr = (void(*)())dlsym(handle,&quot;_Z3foov&quot;);

    (*fooPtr)();
    dlclose(handle);
    return 0;
}</code></pre>
<hr>
<h5 id="참고-자료">&lt;참고 자료&gt;</h5>
<p>코드없는 프로그래밍
<a href="https://prod.velog.io/@kjh3865/ExternAndStatic">[C/C++] extern, static</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++] 메모리 구조 (Stack/Heap)]]></title>
            <link>https://velog.io/@sehee-jj/C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0-StackHeap</link>
            <guid>https://velog.io/@sehee-jj/C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0-StackHeap</guid>
            <pubDate>Mon, 29 Sep 2025 08:57:52 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-c-메모리-구조-스택-vs-힙">📚 C++ 메모리 구조: 스택 vs 힙</h2>
<h3 id="🔸-스택-메모리-stack-memory">🔸 스택 메모리 (Stack Memory)</h3>
<p><strong>구조와 동작</strong></p>
<ul>
<li>함수 단위로 스택 프레임이 생성됨</li>
<li>함수 호출 시 스택에 쌓이고, 함수 종료 시 자동으로 제거됨</li>
<li>지역 변수, 매개 변수, 반환 주소, this 포인터(필요시)등이 포함됨</li>
</ul>
<p><strong>특징</strong></p>
<ul>
<li>객체가 스택에 할당될 때는 멤버 변수 공간만 할당됨</li>
<li>멤버 함수는 코드 영역에 저장되어 모든 객체가 같은 함수 주소를 공유함</li>
</ul>
<h3 id="🔸-힙-메모리-heap-memory">🔸 힙 메모리 (Heap Memory)</h3>
<p><strong>C++에서의 사용법</strong></p>
<ul>
<li>malloc, calloc 함수 사용 금지함 (객체 생성 불가능)</li>
<li>new/delete 연산자를 사용함</li>
<li>delete를 안 하면 메모리 누수가 발생함</li>
</ul>
<p><strong>메모리 누수 방지법</strong></p>
<ul>
<li>스마트 포인터 사용을 권장함</li>
<li>배열의 경우 vector 컨테이너를 사용함</li>
</ul>
<h3 id="⚖️-성능-및-특성-비교">⚖️ 성능 및 특성 비교</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>스택</th>
<th>힙</th>
</tr>
</thead>
<tbody><tr>
<td><strong>할당/해제 속도</strong></td>
<td>빠름 (top에서만 작업)</td>
<td>느림 (복잡한 관리 필요)</td>
</tr>
<tr>
<td><strong>메모리 크기</strong></td>
<td>제한적 (stack overflow 위험)</td>
<td>큰 메모리 할당 가능</td>
</tr>
<tr>
<td><strong>할당 방식</strong></td>
<td>정적 할당</td>
<td>동적 할당</td>
</tr>
<tr>
<td><strong>생명주기</strong></td>
<td>함수 스코프에 의존</td>
<td>프로그래머가 직접 관리</td>
</tr>
</tbody></table>
<h3 id="💡-힙-메모리의-장점">💡 힙 메모리의 장점</h3>
<p>힙 메모리는 다음 세 가지 주요 문제를 해결함</p>
<ol>
<li><strong>Life Cycle</strong> - 함수 스코프를 벗어나도 데이터 유지함</li>
<li><strong>Large Size</strong> - 스택 오버플로우 없이 대용량 데이터 처리함</li>
<li><strong>Dynamic Allocation</strong> - 런타임에 필요한 만큼 메모리 할당함</li>
</ol>
<hr>
<h5 id="참고-자료">&lt; 참고 자료 &gt;</h5>
<p>코드없는 프로그래밍
<a href="https://nybot-house.tistory.com/40">힙 메모리에 대한 모든 것</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL [25.09.03 ~ 25.09.09] - 면접스터디 :: 비선형 자료구조]]></title>
            <link>https://velog.io/@sehee-jj/TIL-25.09.03-25.09.09-%EB%A9%B4%EC%A0%91%EC%8A%A4%ED%84%B0%EB%94%94-%EB%B9%84%EC%84%A0%ED%98%95-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@sehee-jj/TIL-25.09.03-25.09.09-%EB%A9%B4%EC%A0%91%EC%8A%A4%ED%84%B0%EB%94%94-%EB%B9%84%EC%84%A0%ED%98%95-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Thu, 11 Sep 2025 14:37:05 GMT</pubDate>
            <description><![CDATA[<p><a href="https://seheesay.notion.site/2639da75fffd80a39a7cec7d7e1d030b">면접 스터디 - 비선형 자료구조</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL [25.08.30] - Lost In Eden 무기 스왑 오브젝트 풀링]]></title>
            <link>https://velog.io/@sehee-jj/TIL-25.08.30-Lost-In-Eden-%EB%AC%B4%EA%B8%B0-%EC%8A%A4%EC%99%91-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%ED%92%80%EB%A7%81</link>
            <guid>https://velog.io/@sehee-jj/TIL-25.08.30-Lost-In-Eden-%EB%AC%B4%EA%B8%B0-%EC%8A%A4%EC%99%91-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%ED%92%80%EB%A7%81</guid>
            <pubDate>Sat, 30 Aug 2025 12:12:51 GMT</pubDate>
            <description><![CDATA[<h1 id="📝-개발일지---무기-시스템-오브젝트-풀링-전환">📝 개발일지 - 무기 시스템 오브젝트 풀링 전환</h1>
<p>오늘은 기존 무기 시스템의 Destroy/SpawnActor 방식을 오브젝트 풀링 방식으로 전면 리팩토링했음</p>
<p>기존 방식은 무기 교체 시마다 객체 생성/삭제로 인한 성능 오버헤드와 데이터 동기화의 복잡성 문제가 있었음</p>
<p>새로운 방식은 미리 생성된 무기 풀을 활용한 활성화/비활성화 전환으로 성능을 개선하고, 무기 상태의 지속적 유지를 통해 데이터 관리를 단순화하는 구조로 개선했음</p>
<hr>
<h2 id="💡-오늘의-5분-기록">💡 오늘의 5분 기록</h2>
<h3 id="1-기존-무기-시스템의-문제점-분석-🔍">1. 기존 무기 시스템의 문제점 분석 🔍</h3>
<p>코드 리뷰 중에 무기 교체 시스템의 성능과 구조적 문제점들을 발견했음</p>
<p><strong>기존 코드의 문제점</strong></p>
<pre><code class="language-cpp">// ❌ 문제 1: 매번 객체 생성/삭제
void APlayerCharacter::EquipWeapon(EGunType GunType)
{
    if (CurrWeapon)
    {
        GunManager-&gt;UpdateGunData(CurrWeapon);  // 데이터 백업
        CurrWeapon-&gt;Destroy();                  // 객체 삭제
    }
    CurrWeapon = GetWorld()-&gt;SpawnActor&lt;AGun&gt;(GunClass); // 새 객체 생성
    GunManager-&gt;SetCurrentGun(CurrWeapon);      // 데이터 복원
}

// ❌ 문제 2: 이중 데이터 관리
TMap&lt;EGunType, int32&gt; OwnedGuns;  // 별도 데이터 저장
void UpdateGunData(AGun* CurrGun); // 무기 → 데이터 동기화
void SetCurrentGun(AGun* NextGun); // 데이터 → 무기 동기화</code></pre>
<p><strong>성능 영향 분석</strong></p>
<ul>
<li>무기 교체 시마다 메모리 할당/해제로 인한 프레임 드롭 가능성</li>
<li>가비지 컬렉션 압박 증가</li>
<li>데이터 동기화를 위한 추가 오버헤드</li>
</ul>
<h3 id="2-오브젝트-풀링-전환-전략-수립-💡">2. 오브젝트 풀링 전환 전략 수립 💡</h3>
<p><strong>핵심 아이디어 &quot;미리 생성 + 활성화 전환 + 단일 데이터 소스&quot;</strong></p>
<p>설계 원칙 설정</p>
<pre><code>성능: 객체 생성/삭제 → 활성화/비활성화
관리: 이중 데이터 → 무기 객체 단일 소스
구조: UObject → UActorComponent</code></pre><p><strong>예상 개선사항:</strong></p>
<ul>
<li>무기 교체 성능: 부드러운 전환</li>
<li>메모리 관리: 안정적인 사용량</li>
<li>코드 복잡도: 데이터 동기화 로직 제거</li>
</ul>
<h3 id="3-gunmanager-구조-재설계-🛠️">3. GunManager 구조 재설계 🛠️</h3>
<p><strong>1단계: 헤더 파일 개선</strong></p>
<pre><code class="language-cpp">// 기존: 데이터만 관리
class UGunManager : public UActorComponent
{
    TMap&lt;EGunType, int32&gt; OwnedGuns;  // 데이터만 저장
};

// 개선: 객체 풀 관리
class UGunManager : public UActorComponent
{
    TMap&lt;EGunType, AGun*&gt; WeaponPool;    // 실제 무기 객체 풀
    TSet&lt;EGunType&gt; OwnedWeapons;         // 소유 무기 관리
};</code></pre>
<p><strong>2단계: 무기 풀 초기화 시스템</strong></p>
<pre><code class="language-cpp">void UGunManager::BeginPlay()
{
    // 모든 무기 타입 미리 생성
    for (auto&amp; WeaponPair : WeaponClasses)
    {
        AGun* WeaponInstance = World-&gt;SpawnActor&lt;AGun&gt;(GunClass);
        WeaponInstance-&gt;SetActorHiddenInGame(true);  // 비활성화
        WeaponPool.Add(GunType, WeaponInstance);
    }
}</code></pre>
<h3 id="4-무기-교체-로직-개선-⚡">4. 무기 교체 로직 개선 ⚡</h3>
<p><strong>기존 방식: 생성/삭제</strong></p>
<pre><code class="language-cpp">// ❌ 비효율적: 매번 새 객체 생성
CurrWeapon-&gt;Destroy();
CurrWeapon = GetWorld()-&gt;SpawnActor&lt;AGun&gt;(GunClass);</code></pre>
<p><strong>개선된 방식: 활성화/비활성화</strong></p>
<pre><code class="language-cpp">// ✅ 효율적: 풀에서 가져와서 활성화
if (CurrWeapon)
{
    CurrWeapon-&gt;SetActorHiddenInGame(true);     // 현재 무기 숨김
    CurrWeapon-&gt;DetachFromActor(...);
}

CurrWeapon = GunManager-&gt;GetWeapon(GunType);   // 풀에서 가져오기
CurrWeapon-&gt;SetActorHiddenInGame(false);       // 새 무기 표시
CurrWeapon-&gt;AttachToComponent(...);</code></pre>
<h3 id="5-데이터-관리-단순화-📊">5. 데이터 관리 단순화 📊</h3>
<p><strong>이중 관리 시스템 제거</strong></p>
<pre><code class="language-cpp">// 기존: 무기 객체 + 별도 데이터 저장
TMap&lt;EGunType, int32&gt; OwnedGuns;              // 데이터 저장소
void UpdateGunData(AGun* Gun);                // 객체 → 데이터
void SetCurrentGun(AGun* Gun);                // 데이터 → 객체

// 개선: 무기 객체가 모든 상태 보유
TMap&lt;EGunType, AGun*&gt; WeaponPool;             // 객체 풀만 관리
AGun* GetWeapon(EGunType type);               // 직접 객체 반환</code></pre>
<p><strong>상태 유지 개선</strong></p>
<ul>
<li>탄약 수, 업그레이드, 커스터마이징 등 모든 무기 상태가 객체에 지속적으로 보존</li>
<li>데이터 동기화 과정에서 발생할 수 있는 상태 손실 위험 제거</li>
</ul>
<h3 id="6-playercharacter-연동-최적화-🎯">6. PlayerCharacter 연동 최적화 🎯</h3>
<p><strong>BeginPlay 단순화</strong></p>
<pre><code class="language-cpp">void APlayerCharacter::BeginPlay()
{
    Super::BeginPlay();
    // 컴포넌트가 자동으로 BeginPlay에서 풀 초기화 수행

    EquipWeapon(EGunType::PISTOL);  // 바로 장착 가능
}</code></pre>
<h3 id="7-무기-획득-시스템-개선">7. 무기 획득 시스템 개선</h3>
<p><strong>AddItem 함수 최적화</strong></p>
<pre><code class="language-cpp">void APlayerCharacter::AddItem(AItem* Item)
{
    AGun* Gun = Cast&lt;AGun&gt;(Item);
    if (Gun)
    {
        EGunType GunType = Gun-&gt;GetGunType();

        // 중복 확인 후 획득
        if (!GunManager-&gt;HasWeapon(GunType))
        {
            GunManager-&gt;AcquireWeapon(GunType);

            // 픽업한 무기 상태를 풀 무기에 복사
            AGun* PoolWeapon = GunManager-&gt;GetWeapon(GunType);
            PoolWeapon-&gt;SetCurrentAmmo(Gun-&gt;GetCurrentAmmo());
        }
    }
}</code></pre>
<h3 id="8-최종-성능-개선-결과-📈">8. 최종 성능 개선 결과 📈</h3>
<p><strong>달성한 개선사항:</strong></p>
<ul>
<li>✅ <strong>무기 교체 성능</strong>: Destroy/SpawnActor 제거로 부드러운 전환</li>
<li>✅ <strong>메모리 안정성</strong>: 객체 생성/삭제 사이클 제거</li>
<li>✅ <strong>상태 관리</strong>: 무기별 개별 상태 지속적 유지</li>
<li>✅ <strong>코드 단순화</strong>: 데이터 동기화 로직 완전 제거</li>
</ul>
<p><strong>성능 벤치마크:</strong></p>
<ul>
<li>무기 교체 지연: 프레임 드롭 위험 → 즉시 전환</li>
<li>메모리 사용: 변동적 → 일정한 사용량</li>
<li>코드 복잡도: 높음 (동기화 로직) → 낮음 (직접 접근)</li>
</ul>
<hr>
<h2 id="📚-개발-참고">📚 개발 참고</h2>
<h3 id="언리얼-엔진-오브젝트-풀링-패턴">언리얼 엔진 오브젝트 풀링 패턴</h3>
<pre><code class="language-cpp">핵심 원칙:
1. 게임 시작 시 필요한 객체들을 미리 생성
2. 사용하지 않는 객체는 비활성화 상태로 대기
3. 필요할 때 활성화/비활성화로 재사용
4. 객체 자체에 모든 상태 정보 보존</code></pre>
<h3 id="uactorcomponent-vs-uobject-선택-기준">UActorComponent vs UObject 선택 기준</h3>
<pre><code class="language-cpp">UActorComponent 사용 시기:
- 특정 액터에 종속된 기능
- 라이프사이클 관리가 중요한 경우
- 블루프린트 접근성이 필요한 경우

UObject 사용 시기:
- 독립적인 데이터 구조체
- 여러 객체에서 공유되는 유틸리티
- 순수한 로직 처리 클래스</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[25.08.12] :: 가디언 앤 시커 프로젝트 24]]></title>
            <link>https://velog.io/@sehee-jj/25.08.12-%EA%B0%80%EB%94%94%EC%96%B8-%EC%95%A4-%EC%8B%9C%EC%BB%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-24</link>
            <guid>https://velog.io/@sehee-jj/25.08.12-%EA%B0%80%EB%94%94%EC%96%B8-%EC%95%A4-%EC%8B%9C%EC%BB%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-24</guid>
            <pubDate>Tue, 12 Aug 2025 17:03:06 GMT</pubDate>
            <description><![CDATA[<h1 id="📝-개발일지---룬-시스템-프리셋-기능-구현-2">📝 개발일지 - 룬 시스템 프리셋 기능 구현 (2)</h1>
<h2 id="👨💻-오늘의-개발-작업">👨💻 오늘의 개발 작업</h2>
<p>오늘은 구축한 프리셋 데이터 구조를 바탕으로 실제 UI에서 프리셋 전환이 동작하도록 구현했음</p>
<p>핵심적으로는 프리셋 버튼 클릭 시 저장확인 팝업 표시, 세이브 파일 호환성 문제 해결, 그리고 최근 사용 프리셋 자동 로드 기능을 완성했음</p>
<p>특히 기존 LoadBoardConfig() 함수가 너무 많은 책임을 가지고 있어서 발생했던 버그들을 함수 분리를 통해 근본적으로 해결했음</p>
<hr>
<h2 id="💡-오늘의-5분-기록">💡 오늘의 5분 기록</h2>
<h3 id="1-프리셋-전환-시-저장확인-로직-구현">1. 프리셋 전환 시 저장확인 로직 구현</h3>
<p><strong>기존 문제점</strong></p>
<ul>
<li>프리셋 버튼 클릭 시 변경사항이 있어도 바로 전환됨</li>
<li>사용자가 실수로 작업한 내용을 잃을 수 있음</li>
</ul>
<p><strong>해결책: 팝업 기반 확인 시스템</strong></p>
<pre><code class="language-cpp">// GS_ArcaneBoardWidget.h에 추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;ArcaneBoard&quot;)
TSubclassOf&lt;class UGS_CommonTwoBtnPopup&gt; PresetSaveConfirmPopupClass;

UPROPERTY()
int32 PendingPresetIndex; // 사용자가 선택한 대기 중인 프리셋

// 프리셋 버튼 함수들 수정
void UGS_ArcaneBoardWidget::OnPresetButton1Clicked()
{
    if (HasUnsavedChanges())
    {
        ShowPresetSaveConfirmPopup(1); // 저장확인 팝업 표시
    }
    else
    {
        SwitchToPreset(1); // 바로 전환
    }
}</code></pre>
<p><strong>팝업 응답 처리</strong></p>
<pre><code class="language-cpp">void UGS_ArcaneBoardWidget::OnPresetSaveYes()
{
    // 현재 프리셋에 저장 후 새 프리셋으로 전환
    ArcaneBoardLPS-&gt;ApplyBoardChanges();
    SwitchToPreset(PendingPresetIndex);
}

void UGS_ArcaneBoardWidget::OnPresetSaveNo()
{
    // 저장하지 않고 바로 전환
    SwitchToPreset(PendingPresetIndex);
}</code></pre>
<h3 id="2-세이브-파일-호환성-문제-해결">2. 세이브 파일 호환성 문제 해결</h3>
<p><strong>발생한 문제</strong></p>
<pre><code>LogClass: Warning: Struct Property SavedRunesByClass has a struct type mismatch 
(tag RunePlacementData(/Script/GAS) != prop ArcaneBoardPresets(/Script/GAS))</code></pre><p>기존 <code>RunePlacementData</code> 구조체로 저장된 파일과 새로운 <code>FArcaneBoardPresets</code> 구조체 간의 타입 불일치</p>
<p><strong>임시 해결: 기존 세이브 파일 삭제</strong></p>
<ul>
<li>개발 단계에서는 기존 파일 삭제로 빠른 해결</li>
<li>향후 버전 관리 시스템 도입 예정</li>
</ul>
<p><strong>새로운 문제 발견: 기본 룬 없음</strong>
세이브 파일 삭제 후 테스트용 룬이 없어서 기능 테스트 불가</p>
<p><strong>기본 룬 시스템 도입</strong></p>
<pre><code class="language-cpp">void UGS_ArcaneBoardLPS::InitializeDefaultRunes()
{
    OwnedRuneIDs.Empty();

    // 모든 플레이어가 기본 룬 2개로 시작
    OwnedRuneIDs.Add(1);
    OwnedRuneIDs.Add(2);

    UE_LOG(LogTemp, Log, TEXT(&quot;기본 룬 초기화 완료: 룬 ID 1, 2&quot;));
}</code></pre>
<h3 id="3-loadboardconfig-함수-리팩토링">3. LoadBoardConfig 함수 리팩토링</h3>
<p><strong>기존 함수의 문제점</strong></p>
<ul>
<li>단일 함수가 너무 많은 책임을 가짐</li>
<li>룬 인벤토리 초기화, 프리셋 로드, 보드 적용, 에러 처리 등 모든 것을 담당</li>
<li>디버깅이 어렵고 예외 케이스 처리가 복잡함</li>
</ul>
<p><strong>함수 분리를 통한 해결</strong></p>
<pre><code class="language-cpp">// 1. 룬 인벤토리 초기화만 담당
void EnsureRuneInvenInit()
{
    if (OwnedRuneIDs.Num() &gt; 0) return; // 이미 초기화됨

    // 세이브 파일에서 룬 데이터만 로드 시도
    // 실패 시 기본 룬으로 초기화
}

// 2. 프리셋 데이터 로드만 담당  
TArray&lt;FPlacedRuneInfo&gt; LoadPresetData(ECharacterClass CharClass, int32 PresetIndex)
{
    // 모든 예외 상황에서 빈 배열 반환
    // 크래시 방지를 위한 안전한 설계
}

// 3. 보드에 데이터 적용만 담당
void ApplyPresetToBoard(ECharacterClass CharClass, const TArray&lt;FPlacedRuneInfo&gt;&amp; PresetData)
{
    BoardManager-&gt;LoadSavedData(CharClass, PresetData);
    BoardManager-&gt;bHasUnsavedChanges = false;
}

// 4. 전체 흐름 조율 (단순화된 메인 함수)
void LoadBoardConfig(int32 PresetIndex)
{
    EnsureRuneInvenInit();                       // 1단계
    TargetPresetIndex = DeterminePresetIndex(); // 2단계  
    PresetData = LoadPresetData();              // 3단계
    ApplyPresetToBoard(PresetData);             // 4단계
}</code></pre>
<p><strong>단일 책임 원칙(SRP) 적용 효과</strong></p>
<ul>
<li>각 함수가 명확한 하나의 책임만 가짐</li>
<li>디버깅 시 문제 구간을 쉽게 특정 가능</li>
<li>개별 기능을 독립적으로 테스트 가능</li>
<li>재사용성 향상</li>
</ul>
<h3 id="4-최근-사용-프리셋-자동-로드-기능">4. 최근 사용 프리셋 자동 로드 기능</h3>
<p><strong>기존 문제</strong>
아케인보드 창을 열면 항상 프리셋 1이 로드됨. 사용자가 프리셋 2에서 작업했어도 다음에 다시 프리셋 1부터 시작해야 함</p>
<p><strong>GetLastUsedPresetIndex 함수 구현</strong></p>
<pre><code class="language-cpp">int32 UGS_ArcaneBoardLPS::GetLastUsedPresetIndex()
{
    // 세이브 파일에서 LastUsedPresetIndex 값 읽기
    const FArcaneBoardPresets&amp; ClassPresets = LoadedSaveGame-&gt;SavedRunesByClass[CurrClass];
    int32 LastUsedIndex = ClassPresets.LastUsedPresetIndex;

    if (LastUsedIndex &gt;= 1 &amp;&amp; LastUsedIndex &lt;= 3)
    {
        return LastUsedIndex;
    }
    else
    {
        return 1; // 기본값
    }
}</code></pre>
<p><strong>LoadBoardConfig에서 최근 프리셋 우선 로드</strong></p>
<pre><code class="language-cpp">void UGS_ArcaneBoardLPS::LoadBoardConfig(int32 PresetIndex)
{
    int32 TargetPresetIndex;
    if (PresetIndex == -1)
    {
        // 최근 사용 프리셋 로드
        TargetPresetIndex = GetLastUsedPresetIndex();
    }
    else
    {
        // 지정된 프리셋 로드
        TargetPresetIndex = PresetIndex;
    }
    // ...
}</code></pre>
<p><strong>Apply 시 최근 사용 프리셋 업데이트</strong></p>
<pre><code class="language-cpp">void UGS_ArcaneBoardLPS::ApplyBoardChanges()
{
    BoardManager-&gt;ApplyChanges();

    // 현재 프리셋에 저장하면서 최근 사용 프리셋으로 기록
    SaveBoardConfig(CurrentPresetIndex);

    UE_LOG(LogTemp, Log, TEXT(&quot;변경사항을 프리셋 %d에 적용&quot;), CurrentPresetIndex);
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>