[말랑 퀴즈] 26/08/23 해답

게시:     수정

카테고리:

태그:

제 말랑말랑 퀴즈 생성기는 이곳에서 확인하실 수 있습니다.

말랑말랑 퀴즈 — 해답지 ✅

날짜: 2026-08-23 문제 수: 5문제 총점: 43.4/50 (87점) ✅ 잘했어요

📊 채점 결과

문항 유형 난이도 결과 점수
Q1 서술형 🔴 어려움 🔺 부분 정답 7.3 / 10
Q2 객관식 🟡 보통 ⭕ 정답 10 / 10
Q3 빈칸 채우기 🟢 쉬움 🔺 부분 정답 9.1 / 10
Q4 OX 🔴 어려움 🔺 부분 정답 7 / 10
Q5 객관식 🟡 보통 ⭕ 정답 10 / 10
합계       43.4 / 50

총평

62점 → 87점. 지금까지 중 가장 좋은 회차입니다. 객관식 두 문제를 모두 맞히셨는데, 특히 Q2는 오답 C가 소멸 순서(파생 → 기본)를 정확히 적어놓고 결론만 뒤집은 함정이었고 D는 “순수 가상을 단순 가상으로 바꾸면 된다”는 그럴듯한 오답이었습니다. 둘 다 피하셨습니다.

Q1(1)(2)가 이번 회차의 백미입니다. ReplicatedUsingDOREPLIFETIME의 역할 분담을 정확히 구분하셨고, (2)에서 “서버 화면”“콜백은 클라이언트에서 실행되기 때문” 을 한 번에 짚으셨습니다. 원인과 증상을 함께 적는 것 — 지난 이틀간 계속 지적됐던 부분인데 이번에 자리를 잡았습니다.

남은 감점은 두 군데로 모입니다. Q1(3)은 (2)에서 이미 답을 반쯤 써놓고도 마무리를 못 하셨습니다. “서버 화면에서 이렇다”를 쓰셨으니 판별 질문은 “서버 창에는 보이는가?” 가 되어야 했습니다. 자신의 앞 답을 활용하지 못한 케이스입니다.

Q4는 판단은 맞았지만 근거가 반대 방향입니다. 함수 템플릿에 inline을 붙이는 것 자체는 문법적으로 아무 문제가 없습니다 — 출처 문서가 명시적 인라인 방법의 예시로 바로 그 코드를 쓰고 있습니다. 명제가 틀린 이유는 다른 데 있습니다.


Q1. 🔴 어려움 — 🔺 7.3/10

문제: AEPPickup에서 ReplicatedUsing만 붙이면 복제가 되는지, 메시 적용을 OnRep_에만 두면 무슨 문제가 생기는지, 그리고 두 버그를 가르는 질문 하나를 서술하시오.

class AEPPickup : public AActor
{
    UPROPERTY(ReplicatedUsing = OnRep_ItemId)
    FName ItemId;

    void InitPickup(FName InItemId);   // 서버가 스폰 직후 호출
    void OnRep_ItemId();
    void ApplyVisual();                // ItemId에 맞는 메시를 적용
};

📝 내 답:

(1) 안된다. GetLifetimeReplicated 함수에서 DOREPLIFETIME을 사용해주어야 한다. 클라이언트에서 실행될 콜백 함수를 지정해주는 역할이다.

(2) 서버 화면에서는 DeveloperSettings에 넣어둔 기본 메시로 보인다. 해당 콜백은 클라이언트에서 실행되기 때문이다.

(3) 서버에서 실행되는가? 정확히는 모르겠다.

정답:

(1) ReplicatedUsing만으로는 복제되지 않는다

GetLifetimeReplicatedProps()등록해야 한다.

void AEPPickup::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME(AEPPickup, ItemId);
}

두 지정자의 역할이 다르다.

  정하는 것
UPROPERTY(ReplicatedUsing = OnRep_ItemId) “복제되면 이 함수를 불러라”
DOREPLIFETIME(AEPPickup, ItemId) “이 프로퍼티를 복제해라”

ReplicatedUsing복제될 때의 콜백을 지정할 뿐이고, 복제 여부 자체는 등록이 정한다. 등록을 빠뜨리면 경고 없이 조용히 복제되지 않는다.

증상은 클라이언트 창에서만 픽업이 안 보이는 것이다. 서버는 자기 값을 직접 대입했으니 멀쩡하다.

(2) OnRep_에만 두면 서버 창에서 안 보인다

OnRep_ 함수는 복제를 받는 쪽에서만 호출된다. 서버는 값을 직접 대입하므로 OnRep_ItemId()절대 불리지 않는다.

PIE 기본 넷 모드가 Play As Listen Server이므로, 서버 창에도 사람이 보는 화면이 있다. 그런데 그 화면을 그리는 경로에서 ApplyVisual()이 한 번도 안 불린다.

증상: 클라이언트 창에서는 보이는데 서버 창에서만 픽업이 안 보인다.

해결은 적용 함수를 빼서 두 경로 모두에서 부르는 것이다.

void AEPPickup::InitPickup(FName InItemId, const FEPItemState& InState)
{
    ItemId = InItemId;
    State  = InState;
    ApplyVisual();      // 서버 경로. 리슨서버/스탠드얼론의 로컬 화면
}

void AEPPickup::OnRep_ItemId()
{
    ApplyVisual();      // 클라이언트 경로
}

(3) 두 버그를 가르는 질문

“서버 창에는 보이는가?”

서버 창 클라 창 원인
보인다 안 보인다 DOREPLIFETIME 누락 (값이 안 건너감)
안 보인다 보인다 ApplyVisual 누락 (OnRep_에만 있음)

증상이 정확히 거울상이라 헷갈리지만, 창 하나만 확인하면 바로 갈린다.

보충 — State는 일부러 복제하지 않는다

이유는 대역폭이 아니라 정보 은폐다. 바닥 무기의 잔탄이 복제되면 치트 클라이언트가 릴러번시 범위 내 모든 픽업을 읽어 12/30짜리 라이플이 바닥에 있다 = 여기서 누가 죽었다” 를 추론할 수 있다. 근거를 대역폭에 두지 않은 것도 의도적이다. 나중에 이 필드가 배열(배낭 내용물)로 바뀌면 “작으니까 상관없다”는 논리는 뒤집히지만, 정보 은폐는 배열이 되어도 그대로이기 때문이다.

🔍 피드백: 세 소문항 각 3.33점으로 배분했습니다.

  • (1) 3.3 / 3.33 — 만점입니다. GetLifetimeReplicatedProps + DOREPLIFETIME이 필요하다는 것, 그리고 ReplicatedUsing“복제될 때 불릴 콜백을 지정할 뿐” 이라는 역할 구분을 정확히 하셨습니다. “클라이언트에서 실행될” 이라고 쓰신 것도 맞습니다 — OnRep_은 복제를 받는 쪽에서만 불리니까요.

  • (2) 3.0 / 3.33원인과 증상 위치를 둘 다 맞히셨습니다. “서버 화면”과 “해당 콜백은 클라이언트에서 실행되기 때문” — 이 문제가 물은 핵심 두 가지입니다.

    미세하게만 짚습니다. 증상을 “기본 메시로 보인다”고 쓰셨는데, 이 시나리오에서는 ApplyVisual() 자체가 한 번도 안 불립니다. 플레이스홀더를 까는 것도 ApplyVisual() 안에서 하는 일이라, 엄밀히는 “아무것도 안 보인다” 가 됩니다. (액터 BP에 메시가 미리 붙어 있다면 그게 보일 수는 있습니다.)

  • (3) 1.0 / 3.33답이 (2)에 이미 있었습니다. “서버 화면에서는 ~하다”를 쓰셨으니, 판별 질문은 자연스럽게 “서버 창에는 보이는가?” 가 됩니다.

    “서버에서 실행되는가?”는 무엇이 실행되는지가 불분명해서 판별 기준으로 쓸 수 없습니다. 이 문제가 요구한 건 눈으로 확인 가능한 하나의 관찰입니다.

    서버 창 클라 창 원인
    보인다 안 보인다 DOREPLIFETIME 누락
    안 보인다 보인다 ApplyVisual 누락

    자기가 방금 쓴 답을 다음 소문항의 재료로 쓰는 습관을 들이면 이런 마무리가 쉬워집니다. “내가 (2)에서 뭘 근거로 서버 화면이라고 했지?” 를 되짚으면 바로 나옵니다.

출처: game_dev/devlog/2026-08-02-EP_Loot-2.md


Q2. 🟡 보통 — ⭕ 10/10

문제: 기본 클래스 생성자가 비가상 함수 init()을 거쳐 순수 가상 함수 logTransaction()을 호출하는 코드에 대한 설명으로 가장 올바른 것은?

class Transaction {
public:
    Transaction() { init(); }
    virtual void logTransaction() const = 0;
private:
    void init() { logTransaction(); }
};

class BuyTransaction : public Transaction {
public:
    virtual void logTransaction() const override { ... }
};

BuyTransaction b;
  • A. init()이 비가상 함수이므로 가상 함수 호출 문제를 우회할 수 있고, BuyTransaction::logTransaction이 정상적으로 호출된다.
  • B. 기본 클래스 생성자가 먼저 실행되는 시점에는 파생 클래스 부분이 아직 초기화되지 않았으므로 가상 함수가 파생 클래스 버전으로 동작하지 않는다. init()을 거친 간접 호출이라 눈에 잘 띄지 않을 뿐, 위험은 직접 호출과 동일하다.
  • C. 소멸자에서는 파생 클래스 → 기본 클래스 순서로 소멸되므로, 소멸자 안에서 가상 함수를 호출하면 파생 클래스 버전이 안전하게 호출된다.
  • D. logTransaction을 순수 가상 함수 대신 기본 구현이 있는 단순 가상 함수로 바꾸면, 생성자에서도 파생 클래스 버전이 호출되므로 문제가 해결된다.

📝 내 답: B

정답: B

해설:

객체 생성은 기본 클래스 → 파생 클래스 순서로 진행된다. BuyTransaction b;를 만들면 Transaction()이 먼저 돌고, 그 시점에 파생 클래스 부분은 아직 초기화되지 않았다.

그래서 C++은 생성 중인 객체를 아직 기본 클래스 타입으로 취급한다. 생성자 안에서 호출된 가상 함수는 파생 클래스 버전으로 내려가지 않는다.

소멸도 마찬가지다. 파생 클래스 → 기본 클래스 순서로 소멸하므로, 기본 클래스 소멸자가 도는 시점에는 파생 부분이 이미 사라져 있다. 그래서 소멸자에서 호출한 가상 함수도 기본 클래스 버전이다.

  • B (정답): 이 코드의 진짜 무서운 점이 간접 호출이라는 것이다. 생성자에 logTransaction()이 직접 적혀 있으면 그나마 눈에 띈다. 그런데 init()이라는 평범한 비가상 함수를 한 겹 거치면, 생성자만 봐서는 가상 함수가 호출된다는 사실이 보이지 않는다. “알면서 하는 실수” 가 아니라 “모르고 하는 실수” 가 되는 지점이다.

  • A (오답): init()이 비가상이라는 건 아무 도움이 안 된다. 문제는 호출하는 함수가 가상이냐가 아니라 호출되는 시점이 생성 중이냐이다.

  • C (오답): 소멸 순서를 정확히 적었지만 결론이 반대다. 파생 클래스가 먼저 소멸하므로, 기본 클래스 소멸자 시점에는 파생 부분이 이미 없다. 실제로도 기본 클래스 버전이 호출된다.

  • D (오답): 단순 가상 함수로 바꿔도 호출되는 것은 여전히 기본 클래스 버전이다. 다만 순수 가상 함수일 때는 정의가 없어 링커 단계에서라도 잡힐 가능성이 있는 반면, 단순 가상 함수는 조용히 기본 구현이 불려서 오히려 더 발견하기 어려워진다.

해결 — 필요한 정보를 생성자 매개변수로 전달한다

class Transaction {
public:
    explicit Transaction(const string& logInfo) {
        logTransaction(logInfo);       // 비가상 함수 호출
    }

    void logTransaction(const string& logInfo) const;   // 비가상
};

class BuyTransaction : public Transaction {
public:
    BuyTransaction(parameters)
        : Transaction(createLogString(parameters)) { ... }

private:
    static string createLogString(parameters);          // 정적 함수
};

logTransaction비가상 함수로 바꿔 다형성 자체를 없애고, 파생 클래스가 정적 함수로 로그 문자열을 만들어 넘긴다. 정적 함수는 객체 상태를 필요로 하지 않으므로 아직 초기화되지 않은 멤버를 건드릴 위험이 원천적으로 없다.

🔍 피드백: 만점입니다. 이 문제에는 함정이 둘 있었는데 모두 피하셨습니다.

C는 소멸 순서를 정확하게 적어놓고 결론만 뒤집은 보기였습니다. “파생 → 기본 순서로 소멸”까지는 맞는 말이라 무심코 고르기 쉬운데, 그 순서가 뜻하는 바는 정반대입니다 — 파생이 먼저 사라지므로 기본 클래스 소멸자 시점에는 파생 부분이 이미 없습니다. 사실을 아는 것과 그 함의를 아는 것이 다르다는 걸 노린 보기였습니다.

D도 “순수 가상 → 단순 가상으로 바꾸면 해결”이라는 자연스러운 오답이었습니다. 오히려 반대로, 단순 가상으로 바꾸면 링커도 못 잡고 조용히 기본 구현이 불려서 더 나빠집니다.

한 문장으로 굳혀두시면 좋습니다. “생성/소멸 중에는 객체가 아직(또는 이미) 그 타입이 아니다.” 가상 함수를 몇 겹 감싸든 이 사실은 변하지 않습니다.

출처: game_dev/cpp/chapter2/2025-05-29-cpp_2_9.md


Q3. 🟢 쉬움 — 🔺 9.1/10

문제: 빈칸을 채우시오.

 ___ table1 (a,b,c) values (1,2,3)
select a,b,c from table1  ___ a=1
update table1  ___ b=2 where a=1

📝 내 답: ① insert / ② where / ③ set

정답:

insert intowhereset

해설:

CRUD 네 가지 작업이 SQL 구문과 1:1로 대응한다.

작업 SQL 명령어 예시
Create (생성) INSERT INTO insert into table1 (a,b) values (1,2)
Read (조회) SELECT ... FROM ... WHERE select * from table1 where a=1
Update (수정) UPDATE ... SET ... WHERE update table1 set b=2 where a=1
Delete (삭제) DELETE FROM ... WHERE delete from table1 where a=1

핵심 패턴을 형태로 기억하면 편하다.

INSERT INTO 테이블명 (필드들) VALUES (값들)
SELECT 필드들 FROM 테이블명 WHERE 조건
UPDATE 테이블명 SET 필드=값 WHERE 조건
DELETE FROM 테이블명 WHERE 조건

주의할 점

  • WHERE 절이 없으면 모든 레코드에 적용된다. updatedelete에서 특히 위험하다.
  • *는 모든 필드를 의미한다.
  • 조건에는 =, >, <, >=, <=, != 등을 쓸 수 있다.

🔍 피드백: 빈칸 3개를 각 3.33점으로 배분했습니다. ②where, ③set은 만점입니다.

①은 insert intointo까지 들어가야 합니다. 명령어를 아신다는 건 분명하므로 2.5점을 드렸습니다. 다만 빈칸 자리가 ___ table1 (a,b,c) values (...) 였으니, insert만 넣으면 문법이 성립하지 않습니다.

네 구문의 전치사 짝을 같이 외워두시면 헷갈리지 않습니다.

INSERT INTO 테이블 ...     ← into
SELECT ... FROM 테이블 ... ← from
UPDATE 테이블 SET ...      ← 전치사 없음
DELETE FROM 테이블 ...     ← from

update만 전치사가 없다는 게 오히려 기억의 고리가 됩니다.

출처: server/game_server/7/2026-03-03-game_server_7_5.md


Q4. 🔴 어려움 — 🔺 7/10

문제: 다음 명제가 참(O)인지 거짓(X)인지 판단하라.

인라인 함수는 대체로 헤더 파일에 들어가 있어야 한다. 함수 템플릿 역시 대개 헤더 파일에 들어가므로, 함수 템플릿을 작성할 때는 inline으로 선언하는 것이 맞다.

📝 내 답: X — “컴파일 시점에 함수가 호출될 부분을 실제 코드로 치환하는 것이기에 템플릿 타입 추론이 안되므로, 함수 템플릿을 사용하면 안된다”

정답: X

해설:

앞의 두 사실은 모두 맞다. 인라인 함수는 대체로 헤더에 들어가고, 함수 템플릿도 대개 헤더에 들어간다. 틀린 것은 그 둘을 이어 붙인 결론이다.

함수 템플릿은 반드시 인라인 함수일 필요가 없다.

“헤더에 있다”와 “인라인이어야 한다”는 서로 다른 이유에서 나온 별개의 성질이다.

  • 인라인 함수가 헤더에 있는 이유: 컴파일러가 호출 지점에 본문을 삽입하려면 그 본문이 보여야 한다.
  • 함수 템플릿이 헤더에 있는 이유: 인스턴스화 시점에 정의가 필요하기 때문이다.

둘 다 “정의가 보여야 한다”는 결론은 같지만, 그것이 인라인화가 이득인지와는 아무 상관이 없다. 템플릿 함수의 본문이 길고 호출 빈도가 낮다면 인라인으로 만들 이유가 없다. “헤더에 있으니까”는 inline을 붙일 근거가 되지 못한다.

애초에 inline은 요청일 뿐이다

인라인은 컴파일러에 대한 요청이며 무시될 수 있다. 다음 경우가 대표적이다.

  • 가상 함수 호출 — 어느 함수가 불릴지 런타임에 정해지므로 본문을 미리 심을 수 없다
  • 함수 포인터 호출
  • 배열의 원소가 객체인 경우 — 생성자·소멸자의 함수 포인터가 필요해서 본문이 실체로 존재해야 한다

생성자와 소멸자는 인라인에 특히 나쁘다. 비어 보여도 실제로는 기본 클래스 생성자 호출, 멤버 초기화, 예외 발생 시의 정리 코드 등이 잔뜩 생성되기 때문이다.

인라인의 대가

단점 내용
코드 부풀림 페이징 횟수 증가, 명령어 캐시 적중률 저하
바이너리 업그레이드 불가 라이브러리가 인라인 함수 본문을 바꾸면 사용자가 전부 재컴파일해야 한다. 일반 함수였다면 링크만 다시 하면 된다
디버깅 곤란 존재하지 않는 함수에는 중단점을 걸 수 없다

🔍 피드백: 판단(7점)은 만점, 근거(3점)는 0점입니다.

X를 고르신 건 정확합니다. “A이고 B이므로 C이다” 형태의 명제에서 결론을 의심하신 감각이 맞았습니다.

다만 근거로 쓰신 “인라인이라 템플릿 타입 추론이 안 된다” 는 사실이 아닙니다. 인라인화와 템플릿 타입 추론은 서로 다른 단계이고 충돌하지 않습니다. 실제로 출처 문서는 이 코드를 명시적 인라인 방법의 예시로 소개하고 있습니다.

template <typename T>
inline const T& max(const T& a, const T& b) {   // 완벽하게 합법이다
    return a < b ? b : a;
}

그리고 “함수 템플릿을 사용하면 안된다”고 맺으셨는데, 명제가 주장한 건 “함수 템플릿에 inline을 붙여야 한다” 였습니다. 부정해야 할 대상이 어긋났습니다.

명제가 틀린 진짜 이유는 추론의 연결고리에 있습니다.

인라인 함수는 헤더에 있다        (참)
함수 템플릿도 헤더에 있다        (참)
∴ 함수 템플릿은 inline이어야 한다  (거짓)

둘이 헤더에 있는 이유가 서로 다릅니다. 인라인은 호출 지점에 본문을 심으려고, 템플릿은 인스턴스화할 때 정의가 필요해서입니다. 결론(“정의가 보여야 한다”)만 같을 뿐, 그게 인라인화가 이득인지와는 무관합니다.

출처 문서의 마지막 줄이 정확히 이 문장입니다. “함수 템플릿이 대개 헤더 파일에 들어간다는 생각으로 inline으로 선언하면 안 된다.” 붙여도 되지만, 헤더에 있다는 것이 붙일 이유는 못 된다는 뜻입니다.

출처: game_dev/cpp/chapter5/2025-09-22-cpp_5_30.md


Q5. 🟡 보통 — ⭕ 10/10

문제: MongoDB 샤드 클러스터(mongos / ConfigDB / Shard#n) 구조에 대한 설명으로 가장 올바른 것은?

  • A. ConfigDB도 결국 같은 mongod이므로, 샤드 정보를 바꿀 때는 db.config.update(...) 같은 일반 질의 구문으로 수정하는 것이 권장된다.
  • B. ConfigDB가 죽으면 모든 샤드를 쓸 수 없게 되므로 별도 서버 기기에 두고 이중화(replica set)를 구성한다. 반면 개별 샤드 하나가 죽더라도 나머지 샤드는 계속 사용할 수 있다.
  • C. 샤드 키가 포함되지 않은 질의 명령을 받으면 mongos는 어느 샤드로 보낼지 알 수 없으므로 그 요청을 거부한다.
  • D. mongos가 샤드 클러스터의 메타데이터를 직접 들고 있으므로, ConfigDB 없이도 클러스터는 정상 동작한다.

📝 내 답: B

정답: B

해설:

세 종류 서버의 역할이 다르다.

구성 요소 역할
mongos 애플리케이션 서버에서 명령을 받아 적절한 샤드로 송수신하는 라우터
mongod (ConfigDB) 샤드 클러스터 전체의 메타데이터 저장 (샤드 목록, 컬렉션 샤딩 키 정보, 청크 분포 등)
mongod (Shard#n) 샤드 키로 구분된 도큐먼트를 나눠 보관

샤드 배정은 해시로 이뤄진다. 샤드 키를 해시 함수에 넣어 나온 정수 값으로 저장할 샤드를 정한다. (예: John → 해시 함수 → 1~4)

  • B (정답): 가용성의 비대칭이 핵심이다. ConfigDB가 죽으면 모든 샤드가 죽는다. 어느 샤드에 무엇이 있는지 알 방법이 사라지기 때문이다. 그래서 별도 서버 기기에 설치하고 이중화(replica set) 를 하도록 되어 있으며, MongoDB가 그 기능을 제공한다. 반면 개별 샤드는 한 대가 죽어도 나머지는 계속 쓸 수 있고, 각 샤드에도 이중화를 걸어 고가용성을 추구할 수 있다.

  • A (오답): ConfigDB는 같은 mongod이지만 역할이 다르다. 메타데이터를 일반 질의 구문(db.config.update(...))으로 직접 건드리면 안 되고, MongoDB가 제공하는 관리 명령어를 써야 한다. sh.addShard(), sh.enableSharding(), sh.shardCollection() 등이 그것이다.

  • C (오답): 거부하지 않는다. 모든 샤드에 브로드캐스트한다. 어디에 있는지 모르니 전부에게 물어보는 것이다. 동작은 하지만 클러스터 전체가 일을 하게 되므로 비효율적이며, 이것이 샤드 키 설계가 중요한 이유다.

  • D (오답): 정반대다. mongos는 메타데이터를 직접 소유하지 않고 ConfigDB에서 얻어 라우팅한다. ConfigDB가 없으면 어디로 보낼지 알 수 없고, 그래서 B에서 말한 단일 실패 지점이 된다.

🔍 피드백: 만점입니다. 이 문제의 핵심은 가용성의 비대칭인데 정확히 짚으셨습니다 — ConfigDB는 죽으면 전체가 멈추고, 개별 샤드는 죽어도 나머지가 산다.

C가 그럴듯한 오답이었습니다. “샤드 키가 없으면 어디로 보낼지 모른다”까지는 맞는 말이라 “그럼 거부하겠지”로 이어지기 쉬운데, 실제로는 모든 샤드에 브로드캐스트합니다. 동작은 하되 클러스터 전체가 일을 하게 되죠. 샤드 키 설계가 성능을 좌우하는 이유가 여기 있습니다.

며칠 전 Q5에서 다룬 매핑 DB의 SPOF와 같은 구조라는 점도 함께 묶어두시면 좋습니다. 라우팅 정보를 한곳에 모으면 편하지만 그곳이 단일 실패 지점이 되고, 그래서 그 지점도 이중화하거나 샤딩한다 — MongoDB의 ConfigDB replica set이 정확히 그 해법입니다.

출처: server/game_server/8/2026-03-13-game_server_8_8.md


📌 복습 포인트

  • Q1 두 버그를 가르는 질문“서버 창에는 보이는가?” 보이면 DOREPLIFETIME 누락, 안 보이면 ApplyVisual 누락 → game_dev/devlog/2026-08-02-EP_Loot-2.md
  • Q3 SQL 전치사 짝INSERT INTO / SELECT FROM / UPDATE(전치사 없음) / DELETE FROMserver/game_server/7/2026-03-03-game_server_7_5.md
  • Q4 함수 템플릿과 inline — 붙여도 합법이다. 다만 “헤더에 있으니까” 가 붙일 이유는 못 된다game_dev/cpp/chapter5/2025-09-22-cpp_5_30.md
  • Q4 헤더에 두는 이유가 서로 다름 — 인라인은 본문을 심으려고, 템플릿은 인스턴스화에 정의가 필요해서game_dev/cpp/chapter5/2025-09-22-cpp_5_30.md

MallangQuiz 카테고리 내 다른 글 보러가기

댓글남기기