[말랑 퀴즈] 26/08/20 해답
카테고리: MallangQuiz
태그: Quiz
제 말랑말랑 퀴즈 생성기는 이곳에서 확인하실 수 있습니다.
말랑말랑 퀴즈 — 해답지 ✅
날짜: 2026-08-20 문제 수: 5문제 총점: 33.4/50 (67점) 🟡 복습이 필요해요
📊 채점 결과
| 문항 | 유형 | 난이도 | 결과 | 점수 |
|---|---|---|---|---|
| Q1 | 서술형 | 🟡 보통 | ❌ 오답 | 0 / 10 |
| Q2 | 객관식 | 🔴 어려움 | ⭕ 정답 | 10 / 10 |
| Q3 | OX | 🔴 어려움 | ⭕ 정답 | 10 / 10 |
| Q4 | 빈칸 채우기 | 🟢 쉬움 | 🔺 부분 정답 | 6.7 / 10 |
| Q5 | 서술형 | 🟡 보통 | 🔺 부분 정답 | 6.7 / 10 |
| 합계 | 33.4 / 50 |
총평
어려움 두 문제를 모두 맞혔습니다. Q2의 GAS 쿨타임 역행은 오답 보기 A와 C가 각각 흔한 오해와 그럴듯한 함정이었는데 정확히 B를 골랐고, Q3은 “Multicast는 나중에 접속한 클라이언트에게 가지 않는다”라는 핵심 근거를 군더더기 없이 짚었습니다. 최근에 정리한 UE5 복제·GAS 내용은 확실히 몸에 붙어 있습니다.
반면 Q1은 0점입니다. 세 소문항에 전부 “템플릿이라 컴파일러가 T의 정보를 모른다”는 하나의 설명을 적용하셨는데, 이 문제는 템플릿과 무관합니다. NamedObject가 템플릿인 것은 곁가지고, 핵심은 멤버가 참조자와 상수라는 점입니다. 같은 오해가 세 번 반복돼 점수가 한꺼번에 빠졌습니다.
Q4는 ①만 비웠고 ②③은 맞혔습니다. Q5는 (1)을 정확히 답했고 (2)의 핵심 아이디어(“영역을 크게 잡아두고 배정”)까지 떠올렸는데 거기서 멈췄습니다. 아는 것과 설명할 수 있는 것 사이의 간격이 이번 감점의 대부분입니다.
Q1. 🟡 보통 — ❌ 0/10
문제: 아래 코드는 마지막 줄 p = s;에서 컴파일이 거부된다. 복사 대입 연산자가 암시적으로 생성되지 못하는 이유를 nameValue와 objectValue 각각에 대해 설명하고, 해결 방법과 기본 생성자가 생성되지 않는 이유를 서술하시오.
template <class T>
class NamedObject {
public:
NamedObject(string& name, const T& value);
// 복사 생성자와 복사 대입 연산자는 선언하지 않았다
private:
string& nameValue; // 참조자
const T objectValue; // 상수
};
string newDog("Persephone");
string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s; // ← 컴파일 에러
📝 내 답:
(1) nameValue는 string형을 참조하기에 괜찮다. objectValue는 템플릿 클래스이므로, 컴파일러는 해당 클래스에 대해 정보가 없기에 문제가 발생한다.
(2) 템플릿 특수화를 사용한다
(3) 컴파일 시점에서 T에 대해 알고 있는 정보가 없기에 발생한다.
정답:
(1) 멤버별 이유
nameValue(참조자): C++의 참조자는 한 번 어떤 객체를 참조하기 시작하면 다른 객체를 참조하도록 바꿀 수 없다.p = s는p.nameValue가newDog대신oldDog를 가리키게 만들어야 하는데, 참조자로는 불가능하다.objectValue(상수): 이미 초기화된 상수의 값은 변경할 수 없다.p.objectValue를s.objectValue의 값으로 덮어쓸 방법이 없다.
두 멤버 모두 “대입”이라는 동작 자체가 성립하지 않으므로, 컴파일러는 복사 대입 연산자를 만들어낼 수 없다. 그래서 암시적으로 생성해주지 않고 컴파일을 거부한다.
(2) 해결 방법
복사 대입 연산자를 직접 정의해주어야 한다. 컴파일러가 알아서 해주길 기대할 수 없는 상황이다.
참고로 이런 사례가 하나 더 있다. 클래스가 복사 대입 연산자를 private로 선언한 기본 클래스에서 파생된 경우에도 암시적 복사 대입 연산자를 가질 수 없다. 파생 클래스의 암시적 복사 대입 연산자는 기본 클래스 부분도 처리해야 하는데, 호출 권한이 없기 때문이다.
(3) 기본 생성자가 생성되지 않는 이유
생성자가 이미 선언되어 있기 때문이다. 컴파일러는 개발자가 생성자를 하나라도 직접 선언하면 기본 생성자를 만들어주지 않는다. 이 클래스에는 NamedObject(string& name, const T& value)가 있으므로 NamedObject<int> obj; 같은 코드는 쓸 수 없다.
보충 — 컴파일러가 암시적으로 만들어주는 함수 4가지
- 기본 생성자
- 소멸자
- 복사 생성자
- 복사 대입 연산자
선언하지 않았을 때 컴파일러가 대신 생성하며, 모두 public 멤버이자 inline 함수로 만들어진다. 이때 소멸자는 기본 클래스의 소멸자가 가상(virtual)이 아니면 비가상 소멸자로 만들어진다.
🔍 피드백: 세 소문항 모두 0점입니다. 답을 쓰셨으니 미응답은 아니지만, 하나의 잘못된 전제가 세 답 전체를 끌고 갔습니다.
그 전제는 “템플릿이라 컴파일 시점에 T를 몰라서 문제가 생긴다”입니다. 이 문제는 템플릿과 무관합니다. NamedObject<int> p(newDog, 2);처럼 T = int로 인스턴스화된 시점에 컴파일러는 T가 무엇인지 완전히 알고 있습니다. 클래스에서 template을 떼고 int objectValue; 대신 const int objectValue;로 써도 똑같이 에러가 납니다. 문제의 원인은 멤버에 붙은 &와 const뿐입니다.
소문항별로 짚으면 이렇습니다.
- (1) 두 멤버의 역할이 정반대로 뒤집혔습니다.
nameValue는 “괜찮다”고 하셨지만 참조자여서 문제가 되는 쪽이고,objectValue는 템플릿이어서가 아니라const여서 문제입니다. 대입이란 결국 “왼쪽 것을 오른쪽 값으로 바꾸는 일”인데, 참조자는 대상을 바꿀 수 없고 상수는 값을 바꿀 수 없습니다. 둘 다 바로 그 이유로 걸립니다. - (2) 템플릿 특수화는 이 에러와 관계가 없습니다. 특수화를 해도 그 특수화 버전 안의 멤버가 여전히 참조자와 상수라면 똑같이 실패합니다. 답은 복사 대입 연산자를 직접 정의하는 것입니다.
- (3) 기본 생성자가 없는 이유는 훨씬 단순합니다. 생성자를 이미 하나 선언했기 때문입니다. 컴파일러는 생성자가 하나라도 있으면 기본 생성자를 만들어주지 않습니다.
한 문장으로 기억하세요. “컴파일러가 못 만드는 게 아니라, 만들어도 말이 안 되는 코드라서 안 만든다.”
출처:
game_dev/cpp/chapter2/2025-04-22-cpp_2_5.md
Q2. 🔴 어려움 — ⭕ 10/10
문제: GAS 쿨다운 표시값이 5.0 → 4.5 → 다시 5.0으로 되돌아가고, 되돌아가는 값이 매번 정확히 처음 값이다. (쿨다운 5.0초, RTT 0.5초) 이 현상의 원인으로 가장 올바른 것은?
- A. 쿨다운 GE는 예측 대상이 아니어서 클라이언트에는 GE가 생기지 않고, 서버본이 도착하는 순간 처음으로 생성되기 때문이다.
- B. 클라이언트가 아는 서버 시각이 다운링크 지연만큼 뒤처져 있어서, 서버본이 도착한 시점에 계산된 경과 시간이 0이 되어 “방금 시작한 것”으로 취급되기 때문이다.
- C. 위젯이 예측본과 서버본 중 항상 큰 값을 고르기 때문이며, 작은 쪽을 고르도록 바꾸면 해결된다.
- D. 예측 키가 catch-up되기 전까지 두 GE의 지속시간이 합산되어 계산되기 때문이다.
📝 내 답: B
정답: B
해설:
클라와 서버가 똑같은 줄을 실행해 GE를 만든다.
// 엔진 GameplayEffect.cpp:4295
AppliedActiveGE = new(GameplayEffects_Internal)
FActiveGameplayEffect(NewHandle, Spec, GetWorldTime(), GetServerWorldTime(), InPredictionKey);
문제는 GetServerWorldTime()이 서버에서는 진짜 값이지만 클라에서는 추정치라는 점이다. 그 추정치는 이렇게 만들어진다.
// 엔진 GameStateBase.cpp:169
const double ServerWorldTimeDelta = ReplicatedWorldTimeSecondsDouble - World->GetTimeSeconds();
ReplicatedWorldTimeSecondsDouble은 서버가 보낼 때의 시각이고 World->GetTimeSeconds()는 클라가 받을 때의 시각인데, 그 사이의 다운링크 지연을 빼주는 항이 없다. 평균과 스무딩만 있고 지연 보정이 없다. 결론적으로 클라가 아는 서버 시각은 항상 다운링크 지연 D만큼 뒤처져 있다.
서버본이 도착하면 시작 시각을 로컬 시계 기준으로 되돌린다.
// 엔진 GameplayEffect.cpp:2948
void FActiveGameplayEffect::RecomputeStartWorldTime(const float WorldTime, const float ServerWorldTime)
{
StartWorldTime = WorldTime - (ServerWorldTime - StartServerWorldTime);
}
괄호 안이 “이 GE가 얼마나 살았나”인데, 도착 시점에 서버본은 실제로 D만큼 살아 있고 ServerWorldTime이 바로 그 D만큼 작다. 둘이 상쇄돼 경과 시간이 0으로 계산되고, 서버본은 “방금 시작한 것”으로 취급된다. 그래서 도착 순간 남은 시간이 전체 Duration이 된다.
내려간 만큼이 RTT, 튀어 오르는 지점이 전체 Duration — 이래서 항상 정확히 처음 값이다.
- A (오답): 흔한 오해다. 쿨다운 GE는
CommitAbility안의 활성화 윈도우에서 적용되므로 예측 대상이 맞고, 클라에도 GE가 생긴다. 엔진GameplayPrediction.h주석도 GameplayEffect 적용은 예측한다고 명시한다. 예측이 안 되는 것은 GE 제거와 주기 효과이며, 여기서 어긋나는 건 적용 여부가 아니라 시각 정합이다. - C (오답): 큰 쪽을 고르는 것도 한몫하지만 원인은 아니다. 작은 쪽을 골라도 소용없다. 예측 키가 catch-up되면 예측본이 제거되고 어차피 서버본만 남기 때문이다.
- D (오답): 지속시간이 합산되지는 않는다. 두 GE는 각각 독립적으로 자기 시작 시각을 갖는다.
해결: 표시값이 절대 늘지 않도록 단조 감소를 강제한다.
// EPSkillSlotWidget.cpp:85
if (LastShownRemaining >= 0.f)
Remaining = FMath::Min(Remaining, LastShownRemaining);
LastShownRemaining = Remaining;
역행이 RTT짜리 정지로 바뀌고, 끝나는 시각은 서버와 정확히 일치한다.
🔍 피드백: 만점입니다. 이 문제의 함정은 A와 C였는데 둘 다 피하셨습니다.
A는 “쿨다운은 예측이 안 된다”를 “클라에 GE가 안 생긴다”로 오해하는 흔한 경로고, C는 위젯이 큰 값을 고르는 게 실제로 한몫하기 때문에 그럴듯하게 보입니다. 하지만 원인은 아닙니다 — 예측 키가 catch-up되면 예측본이 사라지고 서버본만 남으니 작은 쪽을 골라도 소용없습니다.
한 가지만 덧붙입니다. 단조 감소 클램프는 표시만 고친 것입니다. 어빌리티 재사용 게이트는 서버본의 태그가 사라져야 열리므로, 0.2초 쿨다운에 RTT 100ms면 실질 발사 간격이 0.3초가 되어 연사가 33% 느려집니다. 표시 문제가 아니라 실제 DPS 차이입니다. 제대로 고치려면 서버가 GE 적용 시 ExactPing/2만큼 지속시간을 깎되, 핑 위조 대비 상한을 걸어야 합니다.
출처:
game_dev/devlog/2026-07-26-EP_GAS-6.md
Q3. 🔴 어려움 — ⭕ 10/10
문제: 다음 명제가 참(O)인지 거짓(X)인지 판단하라.
HP에COND_OwnerOnly를 걸어 적의 체력을 숨기고, 사망은UFUNCTION(NetMulticast, Reliable) void Multicast_Die();로 통보한다.Reliable이므로 나중에 접속한 클라이언트도 이미 죽어 있는 캐릭터를 시체 상태로 올바르게 보게 된다.
📝 내 답: X — “Multicast는 나중에 접속한 클라이언트에게는 정보를 보내주지 않는다. 그러므로 서버는 따로 처리해준다.”
정답: X
해설:
앞부분(HP를 숨겼기 때문에 사망을 따로 알려야 하고, 그것이 Reliable이어야 한다)까지는 맞다. 틀린 것은 마지막 결론이다.
Reliable이 보장하는 것은 “보낸 대상에게 반드시 도착한다” 이지, “나중에 온 사람에게도 간다” 가 아니다.
Multicast RPC는 호출된 그 순간 접속해 있고 릴러번트한 클라이언트에게만 간다.
그래서 사망 이후에 접속한 플레이어는 Multicast_Die를 아예 받지 못하고, 시체가 서 있는 것을 보게 된다.
bIsDead 같은 복제 변수 + OnRep 이었다면 늦게 접속한 클라도 초기 복제로 값을 받아 OnRep이 호출되므로 해결됐을 문제다. 실제로 같은 프로젝트의 AEPCorpse가 결국 그 방식을 택했고, EquippedWeapon이 OnRep_EquippedWeapon으로 부착과 LinkAnimClassLayers를 처리하는 것도 같은 이유다.
RPC는 “그 순간 그 자리에 있던 사람”에게만 간다.
| 상황 | 도구 |
|---|---|
| 놓치면 상태가 영구히 어긋남 | Reliable |
| 다음 메시지가 곧 덮어씀 | Unreliable |
| 늦게 접속한 사람도 알아야 함 | RPC 말고 복제 변수 + OnRep |
🔍 피드백: 판단 7점, 근거 3점 모두 만점입니다. “Multicast는 나중에 접속한 클라이언트에게는 정보를 보내주지 않는다” 는 이 문제가 묻는 핵심을 정확히 한 문장으로 짚은 것입니다. Reliable이라는 단어에 낚이지 않으셨습니다.
다만 마지막 “서버는 따로 처리해준다”가 다소 두루뭉술합니다. 구체적으로는 RPC를 쓰지 말고 bIsDead 같은 복제 변수 + OnRep으로 바꾸는 것입니다. 복제 변수는 늦게 접속한 클라에게도 초기 복제 시점에 값이 전달되면서 OnRep이 불리기 때문에, RPC가 못 하는 일을 해냅니다.
여기까지 답을 끌고 가면 “RPC냐 복제 변수냐”를 고르는 기준이 손에 남습니다. 늦게 온 사람도 알아야 하면 복제 변수, 그 순간의 이벤트면 RPC입니다.
출처:
game_dev/devlog/2026-03-01-EP_Replication-5.md
Q4. 🟢 쉬움 — 🔺 6.7/10
문제: 빈칸을 채우시오.
std::type_info::name은 전달된 인자를 템플릿 함수에 ① ___ 매개변수로 전달된 것처럼 취급하기 때문에, ② ___ · volatile · ③ ___를 무시해버린다.
📝 내 답: ?, const, reference
정답:
① 값 전달 (by value) ② const ③ 참조 (reference)
해설:
typeid(param).name()이 class Widget const *를 출력하지만 param의 실제 형식은 class Widget const * const &다. const와 참조가 사라진 것을 확인할 수 있다.
이는 std::type_info::name이 인자를 값으로 전달받은 것처럼 다루기 때문이다. 값 전달에서는 원본의 const·volatile·참조성이 떨어져 나가는데, 그 규칙이 그대로 적용돼버린다.
따라서 std::type_info::name의 출력은 힌트로만 써야 한다. IDE 편집기가 보여주는 형식도 마찬가지로 완전히 믿으면 안 된다.
정확하게 보려면 Boost의 type_id_with_cvr을 쓴다. 이름의 cvr = const, volatile, reference이며 셋을 모두 보존한다.
#include <boost/type_index.hpp>
template<typename T>
void f(const T& param) {
using boost::typeindex::type_id_with_cvr;
cout << "T = " << type_id_with_cvr<T>().pretty_name() << '\n';
cout << "param = " << type_id_with_cvr<decltype(param)>().pretty_name() << '\n';
}
// 출력
// T = class Widget const *
// param = class Widget const * const & ← 정확하다
연역된 형식을 파악하는 세 시점을 정리하면 이렇다.
| 시점 | 방법 | 주의 |
|---|---|---|
| 코드 작성/수정 | IDE 편집기에 마우스 올리기 | 복잡한 형식에서는 도움이 안 될 수 있음 |
| 컴파일 시점 | 선언만 한 템플릿(TD)으로 의도적 오류 유발 |
진단 메시지에 형식이 찍힘 |
| 실행 시점 | typeid().name() / Boost type_id_with_cvr |
앞은 부정확, 뒤는 cvr 보존 |
🔍 피드백: 빈칸 3개를 각 3.33점으로 배분해 ②③ 두 개를 맞혀 6.7점입니다. ②const와 ③reference는 정확합니다.
①은 ?로 비우셨는데, 답은 값 전달(by value) 입니다. 사실 ②③을 이미 맞히셨다면 ①도 유도할 수 있었습니다. const와 참조가 왜 사라지는지를 거꾸로 생각해보면 됩니다 — 값으로 복사해서 받는 순간 원본의 const성과 참조성은 의미가 없어지니 떨어져 나갑니다. std::type_info::name이 딱 그 규칙을 적용해버리는 것입니다.
기억을 붙잡을 고리 하나. Boost의 함수 이름이 type_id_with_cvr인데 cvr = const, volatile, reference입니다. “cvr을 잃어버리는 게 typeid, 보존하는 게 type_id_with_cvr” 로 묶어두면 세 빈칸이 한꺼번에 따라옵니다.
출처:
game_dev/moderncpp/1/2026-04-06-moderncpp_1_4.md
Q5. 🟡 보통 — 🔺 6.7/10
문제: ID를 해시해 샤드 인덱스를 얻는 방식으로 유저 DB를 수평 확장했다. 샤드를 10개에서 11개로 늘릴 때 (1) 왜 단순하지 않은지, (2) 일관된 해시 알고리즘이 이를 어떻게 완화하는지, (3) 매핑 DB 방식의 이점과 위험을 서술하시오.
📝 내 답:
- 해시 값을 재설정해주어야한다. 서버가 잠시 정지된다.
- 미리 영역을 크게 잡아두고 배정시키는 것으로 기억난다. 자세히는 모르겠다.
- 매핑 DB에 병목이 발생할 수 있다.
정답:
(1) 증설이 단순하지 않은 이유 — 리해시
해시 함수의 결과를 샤드 개수로 나눠 인덱스를 얻는 구조에서는, 샤드 개수가 달라지는 순간 거의 모든 키의 목적지가 바뀐다. 즉 리해시가 발생해 기존 레코드를 재배치해야 한다.
운영상 문제는 여기서 나온다. 리해시해야 하는 레코드가 많으면 그동안 게임 서버를 장시간 점검(다운타임) 해야 한다.
이를 줄이는 접근은 세 가지다.
1. 레코드 이동 개수 최소화 → 일관된 해시 알고리즘
2. 이동할 일이 없게 만듦 → 매핑 DB
3. 레코드 이동 크기 최소화
(2) 일관된 해시 알고리즘
핵심 차이는 해시 값과 샤드를 일대일로 묶지 않는다는 것이다.
- 일반적인 해시 테이블: 항목이 해시 함수 값에 일대일 대응한다. 그래서 개수가 바뀌면 대응이 통째로 흔들린다.
- 일관된 해시: 항목 개수를 크게 고정해두고(예: 0~999), 샤드 하나가 해시 값의 집합(구간)을 맡는다.
| 해시 | 항목 | 샤드 |
|---|---|---|
| hash(“A”) | 17 | 1 |
| hash(“B”) | 23 | 1 |
| hash(“C”) | 745 | 2 |
이 구조에서는
- 샤드 추가: 기존 샤드가 맡던 해시 집합을 두 샤드가 나눠 가진다.
- 샤드 삭제: 그 샤드가 맡던 집합을 인접 샤드에 나눠 준다.
즉 샤드 개수 변동 1개당 샤드 1개 분량만 재배치하면 되고, 일부 레코드만 리해시된다. 해시 테이블은 링형으로 보아야 하며(마지막 샤드와 첫 샤드가 인접), 다만 샤드가 크게 늘거나 줄면 결국 많은 양을 리해시해야 한다는 한계는 남는다.
(3) 매핑 DB
입력(키) → 샤드 인덱스 대응을 담은 DB를 따로 두는 방식이다. 접속 절차는 이렇게 된다.
1. 클라이언트 → 인증 서버 중 하나에 접속
2. 인증 서버 → 매핑 DB에 질의해 DB 인덱스(샤드 넘버) 획득
3. 인증 서버 → 해당 유저 DB에 CRUD 질의
- 이점: 목적지가 계산이 아니라 기록으로 정해지므로, 샤드를 추가할 때 레코드 이동 없이 새 샤드를 채우기만 하면 된다. 리해시 자체가 사라진다. (단, 샤드 제거 시에는 매핑 DB의 레코드 이동과 유저 DB의 위치 반영이 필요하다.)
- 위험: 모든 접속이 매핑 DB를 거치므로, 여기에 과부하가 걸리면 수평 확장을 한 의미가 없어진다. 단일 실패 지점(SPOF) 이 된다. 그래서 매핑 DB 자체에도 확장성을 부여해 레코드를 여러 샤드로 나눈다.
🔍 피드백: 세 소문항 각 3.33점으로 배분했습니다.
- (1) 3.3 / 3.33 — 만점입니다. 리해시가 일어난다는 것과 그 때문에 서버를 세워야 한다는 것, 두 핵심을 모두 짚으셨습니다. 굳이 보태자면 교재는 “잠시”가 아니라 장시간 점검이라고 표현합니다. 레코드가 수백만 건이면 몇 시간짜리 작업이 되기 때문이고, 바로 그래서 아래 두 대안이 나옵니다.
- (2) 1.7 / 3.33 — “미리 영역을 크게 잡아두고 배정시킨다”가 정확히 핵심입니다. 기억을 제대로 꺼내셨는데 거기서 멈추신 게 아깝습니다. 이어서 두 가지만 붙이면 완성됩니다. 첫째, 일반 해시는 항목이 해시 값에 일대일 대응하지만 일관된 해시는 샤드 하나가 해시 값의 집합(구간) 을 맡습니다. 둘째, 그 결과 샤드 증감 1개당 샤드 1개 분량만 재배치하면 되어 일부 레코드만 리해시됩니다. 문제가 “일반적인 해시 테이블과 무엇이 다른지 포함해”라고 물은 게 이 부분입니다.
- (3) 1.7 / 3.33 — 위험(SPOF)은 정확합니다. “병목”이라는 표현도 맞습니다. 그런데 문제가 이점과 위험을 둘 다 물었는데 이점이 빠졌습니다. 매핑 DB의 이점은 샤드를 추가할 때 레코드를 옮길 필요가 없다는 것입니다. 목적지가 계산이 아니라 기록으로 정해지니 리해시 자체가 사라집니다. (다만 샤드 제거 시에는 여전히 이동이 필요합니다.)
세 방식을 한 줄로 묶어두면 좋습니다. 일관된 해시 = 옮길 양을 줄인다, 매핑 DB = 옮길 일 자체를 없앤다. 대신 매핑 DB는 SPOF가 되므로 그것도 샤딩한다.
출처:
server/game_server/9/2026-03-26-game_server_10_2.md
📌 복습 포인트
- Q1 암시적 복사 대입 연산자 — 원인은 템플릿이 아니라 멤버의
&와const. 참조자는 대상을 못 바꾸고, 상수는 값을 못 바꾼다 →game_dev/cpp/chapter2/2025-04-22-cpp_2_5.md - Q1 해결 방법 — 템플릿 특수화가 아니라 복사 대입 연산자를 직접 정의 →
game_dev/cpp/chapter2/2025-04-22-cpp_2_5.md - Q1 기본 생성자 — 생성자를 하나라도 선언하면 컴파일러가 기본 생성자를 안 만든다 →
game_dev/cpp/chapter2/2025-04-22-cpp_2_5.md - Q3 RPC vs 복제 변수 — 늦게 접속한 사람도 알아야 하면 복제 변수 +
OnRep→game_dev/devlog/2026-03-01-EP_Replication-5.md - Q4 typeid의 한계 — 인자를 값 전달로 취급해 cvr(const·volatile·reference)을 잃는다 →
game_dev/moderncpp/1/2026-04-06-moderncpp_1_4.md - Q5 일관된 해시 — 샤드 하나가 해시 값의 집합을 맡아, 증감 1개당 샤드 1개 분량만 재배치 →
server/game_server/9/2026-03-26-game_server_10_2.md - Q5 매핑 DB의 이점 — 위험(SPOF)뿐 아니라 샤드 추가 시 레코드 이동이 없다는 이점까지 →
server/game_server/9/2026-03-26-game_server_10_2.md
댓글남기기