[말랑 퀴즈] 26/09/14 문제

게시:     수정

카테고리:

태그:

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

말랑말랑 퀴즈 📝

날짜: 2026-09-14 문제 수: 6문제


Q1. 🟡 보통

상속 관계에서 이름이 어떻게 탐색되고, 어떻게 가려지는지에 대한 문제다.

class Base {
private:
    int x;
public:
    virtual void mf1() = 0;
    virtual void mf1(int);

    virtual void mf2();

    void mf3();
    void mf3(double);
};

class Derived : public Base {
public:
    virtual void mf1();
    void mf3();
    void mf4();
};

int main() {
    Derived d;
    int x;

    d.mf1();      // ✅ Derived::mf1
    d.mf1(x);     // ❌ 에러!
    d.mf2();      // ✅ Base::mf2
    d.mf3();      // ✅ Derived::mf3
    d.mf3(x);     // ❌ 에러!
}

(1) Derived::mf4() 안에서 mf2()를 호출하면 컴파일러는 어떤 순서로 유효범위를 뒤지는가? 찾는 순서를 처음부터 끝까지 쓰시오. (못 찾았을 때 마지막으로 가는 곳까지)

(2) d.mf1(x)d.mf3(x)가 에러인 이유를 설명하시오. 그리고 아래 두 사실이 이 현상에 영향을 주는지 각각 답하시오.

  • Base::mf1(int)Derived::mf1()매개변수가 다르다
  • mf1가상 함수이고 mf3비가상 함수다

(3) 이 현상을 피하는 방법이 두 가지 있다.

  • DerivedBasepublic 상속할 때 쓰는 방법을 코드로 쓰시오. 그리고 그 선언을 어느 접근 영역에 두어야 하며 왜 그런지 쓰시오.
  • DerivedBaseprivate 상속하면서 mf1() 버전 하나만 물려받고 싶다면 ㉠은 쓸 수 없다. 왜 안 되며, 대신 무엇을 쓰는가? 코드로 쓰시오.

출처: game_dev/cpp/chapter6/2025-10-19-cpp_6_33.md

📝 내 풀이:

(1) 현재 클래스에서 mf2 함수 탐색 -> 부모 클래스에서 mf2 함수 탐색

(2) 함수 선언이 덮이는 바람에 모호해졌기 때문
ㄱ: 매개변수가 다르지만, 이는 함수 이름을 동일하게 덮는다.
ㄴ: 가상/비가상, 매개변수 여부와 상관없이 함수 이름을 덮는다.

(3) ㄱ: Base::mf1(int), Base::mf3() 와 같이 명확하게 사용한다.
ㄴ: 이 문제는 어설프게 기억나서 다시 한번 봐야할것같다.


Q2. 🔴 어려움

C++의 캐스팅에 대한 문제다. 아래 두 코드를 보자.

코드 ①

class Window {
public:
    virtual void onResize() { ... }
};

class SpecialWindow : public Window {
public:
    virtual void onResize() {
        static_cast<Window>(*this).onResize();   // 기본 클래스의 onResize를 먼저 호출하려는 의도
        ...                                      // 그 다음 SpecialWindow 고유 처리
    }
};

코드 ②

typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;

for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) {
    if (SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get()))
        psw->blink();
}

이 두 코드에 대한 설명으로 가장 올바른 것은?

  • A. 코드 ①의 static_cast<Window>(*this).onResize()는 현재 객체의 기본 클래스 부분에 대해 Window::onResize를 호출하는 올바른 방법이다. Window::onResize();라고 쓰는 것과 동작이 같으며, 캐스트를 명시했으므로 오히려 의도가 더 분명하다.
  • B. 코드 ②의 dynamic_cast는 vtable 포인터를 한 번 비교하는 것으로 끝나므로 빠르다. 따라서 SpecialWindow1, SpecialWindow2, SpecialWindow3… 를 else if로 이어 붙이는 폭포식 dynamic_cast 도 가독성이 떨어질 뿐 성능상 문제는 없다.
  • C. 코드 ①의 static_cast<Window>(*this)*this의 기본 클래스 부분에 대한 사본을 임시로 생성하므로, onResize가 현재 객체가 아니라 그 임시 객체에 대해 호출된다. 올바른 방법은 Window::onResize();다. 코드 ②의 dynamic_cast클래스 이름에 대한 문자열 비교 기반이라 매우 느리므로, 원하는 조작을 가상 함수 집합으로 정리해 기본 클래스에 넣어 캐스팅 자체를 없애는 편이 낫다.
  • D. 신형 캐스트 네 가지(const_cast, dynamic_cast, reinterpret_cast, static_cast) 중 객체의 상수성을 없앨 수 있는 것은 static_castconst_cast이다. static_cast는 암시적 변환을 강제하는 것이므로 const를 떼는 방향도 가능하기 때문이다.

출처: game_dev/cpp/chapter5/2025-07-09-cpp_5_27.md

📝 내 선택: C

A: *this는 사본이므로 동작이 다르다. B: dynamic_cast 자체가 무엇인지 까먹었는데, C가 맞는 것 같아서 문자열 비교 기반이었던 것 같다.
C가 맞다면 성능상 문제가 생길 것이다. D: 상수성을 없앨수 있는건 const_cast 뿐이다.


Q3. 🔴 어려움 · 📌 오답노트 (2번째 복습)

당신은 게임 서버 엔지니어다. 현재 유저 DB는 샤드 10개로 나뉘어 있고, 인증 서버는 ID → 해시 함수 → 샤드 인덱스 방식으로 어느 샤드에 질의할지 정한다.

[클라이언트] ──▶ [인증 서버] ──hash(ID) % 10──▶ [샤드 0 ~ 9]

유저가 늘어 샤드를 늘려야 하는데, 팀에서 두 가지 제안이 올라왔다.

  • 제안 A — 지금 방식은 유지하되, 해시를 일관된 해시 알고리즘으로 바꾼다.
  • 제안 B — 해시 대신 매핑 DB를 두고, ID → 샤드 인덱스 표를 조회한다.

(1) 먼저 현재 방식의 문제를 짚어야 한다. 지금 상태에서 샤드를 10개 → 11개로 늘리면 무슨 일이 왜 벌어지고, 그것이 운영에 어떤 결과를 낳는가? 그리고 이 방식에서 해시 함수에 넣는 키(ID)를 무엇이라 부르는가?

(2) 제안 A(일관된 해시) 를 검토한다.

  • ㉠ 일반 해시 테이블과의 차이를 “항목 수”와 “샤드-해시 값의 대응 관계” 두 가지로 설명하시오.
  • ㉡ 샤드 10개 → 11개일 때, 이 방식에서는 어느 샤드의 어떤 레코드만 옮기면 되는가?
  • ㉢ 이 방식이 여전히 비싼 상황은 무엇인가? (어떤 종류의 변경일 때 이득이 사라지는지)

(3) 제안 B(매핑 DB) 를 검토한다.

  • ㉠ 이 방식에서 샤드를 추가할 때 레코드 이동이 0인 이유를, 제안 A와 대비해 “계산”과 “조회” 라는 두 단어로 설명하시오.
  • ㉡ 이 구조가 새로 떠안는 위험의 이름(한국어와 약어 모두)과, 그에 대한 대응책을 쓰시오.

(4) 아래 두 상황에서 각각 A와 B 중 무엇을 고르겠는가? 이유와 함께 답하시오.

  • 상황 ㉮: 앞으로 매달 샤드를 1개씩 천천히 늘릴 계획이다.
  • 상황 ㉯: 내년에 대형 업데이트로 10개 → 40개로 한꺼번에 늘려야 한다.

출처: server/game_server/9/2026-03-26-game_server_10_2.md 오답노트: wn-1da60a18 · 2026-09-09 최초 오답 · 이번이 2번째 복습

📝 내 풀이:

(1) 전체적으로 해시 재할당이 발생해, 서버가 오래 멈추게 된다.
해시 키

(2) ㄱ: 항목 수를 미리 여러개 할당해 놓고, 샤드와 해시가 n:1 대응된다.
ㄴ: 샤드의 영향을 받은 범위만 레코드를 옮기고 해시 재할당을 해주면 된다.
ㄷ: 많은 양의 변경이 들어올때.

(3) ㄱ: 따로 계산할 필요없이 매핑 DB는 키를 기반으로 조회하여 값만 얻으면 되기 때문에, 추가만 하면 된다.
ㄴ: DB 오버헤드(overhead). 매핑 DB 서버도 분산하여 둔다.

(4) 가: A, 나: B


Q4. 🟢 쉬움

빈칸을 채우시오.

여러 스레드가 같은 데이터를 건드릴 때 그 데이터의 상태를 예측할 수 없게 되는 상황을 ① ___ (또는 데이터 레이스)라고 한다. 이를 막으려면 공유 데이터에 대해 두 가지 성질을 보장해야 한다.

성질 예시
___ 하나의 작업 단위가 절대 쪼개지지 않게 한다 num++가 읽기·더하기·쓰기 사이에서 끊기지 않게
___ 여러 멤버 변수가 항상 서로 어울리는 상태여야 한다 배열의 포인터와 크기 정보가 어긋난 순간에 다른 스레드가 끼어들지 못하게

이 두 성질을 보장하는 기술을 통틀어 ④ ___ 라 하며, 대표적으로 다음 세 가지가 있다.

  • ___ (Critical Section)
  • ___ (Mutex)
  • ___ (Lock)

핵심은 공유 데이터에 접근하는 순간만큼은 다른 스레드가 접근하지 못하도록 막는 것이다.

출처: server/game_server/1/2025-05-30-game_server_1_5.md

📝 내 답: 레이스 컨디션(경쟁 상태), 원자성, 일관성, ?, 임계 영역, 뮤텍스, 잠금


Q5. 🟡 보통

Widget 객체를 사용하는 프로그램에서 각 멤버가 몇 번 호출되는지 프로파일하려 한다. 일정 시간마다 자동으로 onTick()을 불러 주는 Timer 클래스가 이미 있다.

class Timer {
public:
    explicit Timer(int tickFrequency);
    virtual void onTick() const;      // 일정 시간이 지날 때마다 자동 호출된다
};

이를 이용해 Widget을 만드는 두 가지 방법이 있다.

방법 ㉠ — private 상속

class Widget : private Timer {
private:
    virtual void onTick() const;      // 재정의
};

방법 ㉡ — 객체 합성

class Widget {
private:
    class WidgetTimer : public Timer {
    public:
        virtual void onTick() const;
    };
    WidgetTimer timer;
};

이 상황에 대한 설명으로 가장 올바른 것은?

  • A. private 상속의 의미는 public 상속과 마찬가지로 is-a다. 그래서 Widget : private Timer라고 쓰면 Widget 객체를 Timer&를 받는 함수에 그대로 넘길 수 있고, Timer의 public 멤버는 Widget에서도 public으로 남는다.
  • B. 방법 ㉠이 더 낫다. private 상속을 쓰면 Widget을 상속한 클래스가 onTick을 다시 재정의하지 못하도록 설계 차원에서 막을 수 있어 통제가 쉽고, 코드도 ㉡보다 짧기 때문이다.
  • C. 방법 ㉡이 권장된다. 이유는 두 가지다 — ① WidgetTimerWidget의 private 중첩 클래스이므로 Widget의 파생 클래스가 onTick을 재정의할 수 없게 설계 차원에서 막을 수 있고, ② WidgetTimer의 정의를 Widget 밖으로 빼내고 포인터만 갖게 하면 컴파일 의존성을 최소화할 수 있다. private 상속은 protected 멤버 접근·가상 함수 재정의·공백 기본 클래스 최적화가 꼭 필요할 때의 예외적 선택이다.
  • D. 두 방법은 완전히 같은 결과를 내므로 취향의 문제다. 다만 private 상속은 인터페이스와 구현을 모두 물려받는 반면 객체 합성은 구현만 가져오므로, 인터페이스 재사용이 필요하면 ㉠을 써야 한다.

출처: game_dev/cpp/chapter6/2025-10-26-cpp_6_39.md

📝 내 선택: C

A: private 상속의 의미는 is-implemented-in-terms-of이다. timer의 public 멤버는 widget에서 private로 남는다.
B: 특정한 상황 빼면 ㄴ이 더 나았던 것으로 기억난다. 그리고 재정의가 가능하다. D: 같은 결과를 내지도 않고 취향의 문제도 아니다.


Q6. 🟡 보통

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

플레이어 간 상호작용은 다른 처리들과 성격이 다르다. PVP에서는 간발의 차이로 승부가 결정되므로 정확해야 하고, 그만큼 높은 성능 수준이 요구된다. 기관총 발사처럼 횟수가 매우 잦을 때도 있다. 결과가 모두 반영되거나 전혀 반영되지 않아야 하는 원자성이 필요하며, 히트 판정처럼 지연 시간에 큰 영향을 받는다.

이런 성질들 때문에 플레이어 간 상호작용은 여러 서버에 적극적으로 분산해야 한다. 한 서버에 몰아 두면 정확성과 성능을 모두 만족시킬 수 없기 때문이다. 특히 응집도가 높은 플레이어들, 즉 서로 자주 부딪히는 플레이어들을 서로 다른 서버에 나눠 두어야 부하가 고르게 퍼져 효과가 크다.

출처: server/game_server/9/2026-03-29-game_server_10_5.md

📝 내 답: X. 플레이어 간 상호작용은 응집도가 높으므로 한 서버에 모아야 한다.
다른 서버에 나누어야 하는 것은 응집도가 낮을 때이다.


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

댓글남기기