2026-03-19 TIL (18일차)
디버깅의 중요성
도전 과제로 넘어가면서 필수 기능들을 여러 클래스(Class)로 분리하고 연결하기 시작했습니다. 프로젝트의 규모가 커질수록 디버깅의 중요성이 커졌습니다.
💡 클래스 분리 시 주의점 기능이 여러 클래스로 쪼개지면, 오류가 났을 때 “어느 클래스의 어느 함수에서부터 잘못된 데이터가 넘어왔는지” 호출 스택을 따라가며 추적하는 능력이 필수적입니다. 디버그 모드와 중단점을 적극 활용
STL map
C++의 std::map은 Key와 Value을 한 쌍으로 묶어 저장하는 자료구조입니다. 내부적으로 정렬되어 있으며, 데이터를 빠르게 검색할 때 유용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <map>
// 1. 맵 선언 (Key: 물약 이름, Value: 재고 수량)
std::map<std::string, int> potionStock;
// 2. 데이터 추가 및 수정
potionStock["힐링포션"] = 5;
potionStock.insert({"마나포션", 3});
// 3. 데이터 검색 (find)
auto it = potionStock.find("힐링포션");
if (it != potionStock.end()) {
std::cout << "힐링포션 재고: " << it->second << "개\n"; // it->first는 Key, it->second는 Value
}
// 4. 데이터 순회
for (const auto& pair : potionStock) {
std::cout << pair.first << " : " << pair.second << "\n";
}
레시피 중복 검색 오류
검색어와 전혀 일치하지 않는 재료가 포함된 레시피가 검색 결과에 노출됨
문제코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
std::vector<PotionRecipe> Recipes_Results;
for (int i = 0; i < this->recipes_.size(); i++)
{
bool isMatch = false;
if (name == recipes_[i].GetpotionName())
{
isMatch = true;
}
else
{
for (int j = 0; j < this->recipes_.size(); j++)
{
for (int z = 0; z < this->recipes_[j].Getingredients().size(); z++)
{
if (this->recipes_[j].Getingredients()[z] == name)
{
isMatch = true;
break;
}
}
}
}
if (isMatch)
{
Recipes_Results.push_back(recipes_[i]);
}
}
else 내부에서 현재 확인 중인 레시피(i)의 재료만 검사해야 하는데, 전체 레시피(j)를 처음부터 다시 순회하도록 이중 루프를 잘못 작성하여 발생한 논리 오류.
1
2
3
4
5
6
7
8
9
10
11
else
{
for (int z = 0; z < this->recipes_[i].Getingredients().size(); z++)
{
if (this->recipes_[i].Getingredients()[z] == name)
{
isMatch = true;
break;
}
}
}
수정
상수의 정확성
AlchemyWorkshop 클래스에서 RecipeManager의 함수를 호출할 때 컴파일 에러가 발생했습니다. 원인은 C++의 엄격한 상수 보장 규칙(const Correctness) 때문이었습니다.
차이: 함수 맨 뒤의 const
클래스의 멤버 함수 맨 뒤에 붙는 const는 단순한 키워드가 아니라 컴파일러와의 약속입니다.
함수 뒤의
const가 가지는 의미
SearchRecipes(...) const;내부에 있는 멤버 변수(데이터)들을 절대 수정하지 않고, 안전하게 읽기 (읽기 전용 선언)FindRecipeByName(...);필요하다면 언제든 클래스 내부 데이터를 수정(수정 가능성 내포)
에러 이유
문제는 AlchemyWorkshop의 GetStockByName 함수에서 시작되었습니다.
1
2
3
4
5
// 1. 여기서 const 약속을 했습니다.
int AlchemyWorkshop::GetStockByName(const std::string& potionName) const {
// 2. 그런데 내부에서 const가 없는 함수를 호출하려 했습니다.
PotionRecipe* recipe = recipeManager_.FindRecipeByName(potionName);
}
GetStockByName은 맨 뒤에 const를 붙임으로써상태를 절대 바꾸지 않겠다고 선언했습니다.
그런데 그 내부에서 const가 안 붙은 FindRecipeByName을 부르려고 하니, 컴파일러가 에러가 발생했습니다.
FindRecipeByName 뒤에 const를 붙이면 되잖아?
포인터의 딜레마 발생
이유: 함수에 const를 붙여서 내부 원본을 건드리지 않겠다고 선언해 놓고, 정작 반환값으로는 원본을 마음대로 뜯어고칠 수 있는 포인터를 호출자에게 넘겨주려 했기 때문입니다.
해결법
객체지향적 설계 분리
HasRecipe 함수 추가: 이 물약이 존재하는지 확인만하고 싶을 때 사용할, const 전용 함수를 구현.
1
2
3
4
5
6
7
8
9
10
// [RecipeManager.h] const가 붙은 안전한 읽기 전용 함수 추가
bool HasRecipe(const std::string& name) const;
// [AlchemyWorkshop.cpp]
int AlchemyWorkshop::GetStockByName(const std::string& potionName) const {
if (recipeManager_.HasRecipe(potionName) == false) {
return -1; // 장부에 없는 물약
}
return stockManager_.GetStock(potionName);
}
