[말랑 퀴즈] 26/10/09 해답

게시:     수정

카테고리:

태그:

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

말랑말랑 퀴즈 — 해답지 ✅

날짜: 2026-10-09 문제 수: 6문제 총점: 27.5/60 (46점) 🔴 다시 공부해요

📊 채점 결과

문항 유형 난이도 결과 점수
Q1 객관식 🟡 보통 ⭕ 정답 10 / 10
Q2 빈칸 채우기 🟡 보통 ⬜ 미응답 0 / 10
Q3 서술형 🔴 어려움 ❌ 0점 처리 0 / 10
Q4 OX 🟡 보통 ❌ 오답 0 / 10
Q5 객관식 🟡 보통 ⭕ 정답 10 / 10
Q6 📌 서술형 🟡 보통 🔺 부분 정답 7.5 / 10
합계       27.5 / 60

총평

C++ 템플릿·다중 상속을 다룬 객관식 두 문제(Q1, Q5)는 보기마다 오답 근거까지 적을 만큼 개념이 탄탄합니다. Q3은 레퍼런스를 보고 풀었다고 직접 0점 처리하셨는데, 내용 자체는 거의 정확했습니다. 반면 Q2(SQL Server 자료형)는 풀지 않았고, Q4는 pair<const K, V>와 pair<K, V>가 다른 타입이라는 점을 놓쳤습니다. 오답노트 Q6은 값 3개와 동기화 기법은 맞혔지만, (2)의 두 번째 현상과 (4)의 대응 설명이 비었습니다.


Q1. 🟡 보통 — ⭕ 10/10

문제: 다음 템플릿 함수와 C++의 인터페이스·다형성에 대한 설명으로 가장 올바른 것은?

template<typename T>
void doProcessing(T& w) {
    if (w.size() > 10 && w != someNastyWidget) {
        T temp(w);
        temp.normalize();
        temp.swap(w);
    }
}
  • A. 클래스의 명시적 인터페이스는 컴파일 중에, 템플릿의 암시적 인터페이스는 프로그램 실행 중에 점검된다.
  • B. 템플릿의 암시적 인터페이스는 함수 시그너처가 아니라 템플릿 안에서 T의 객체에 사용된 유효 표현식들로 이루어지며, T가 그 표현식을 지원하지 않으면 템플릿 인스턴스화 단계, 즉 컴파일 중에 에러가 난다.
  • C. 위 코드에서 w.size() > 10이 유효하려면, T의 size()는 반드시 정수 타입을 반환해야 한다.
  • D. 가상 함수를 통한 다형성은 컴파일 중에 결정되고, 템플릿 인스턴스화를 통한 다형성은 프로그램 실행 중에 결정된다.

📝 내 선택: B (A·C·D 오답 근거도 함께 작성)

정답: B

해설:

  • A (✗): 암시적 인터페이스와 명시적 인터페이스는 모두 컴파일 도중에 점검됩니다.
  • B (✓): 암시적 인터페이스는 사용 패턴에서 추론되는 유효 표현식으로 구성되고, 지원하지 않는 타입(예: size()가 없는 클래스)으로 호출하면 컴파일이 되지 않습니다.
  • C (✗): T의 operator>가 재정의되어 있을 수 있으므로 size()가 꼭 정수를 반환할 필요는 없습니다. 최종 표현식이 bool과 호환되기만 하면 됩니다.
  • D (✗): 반대입니다. 가상 함수 다형성은 실행 중(동적 바인딩), 템플릿 인스턴스화 다형성은 컴파일 중에 결정됩니다.

🔍 피드백: 정답입니다. 오답 보기 세 개를 모두 정확한 이유로 반박했습니다. 특히 C에서 “> 연산자가 bool만 반환하면 된다”는 설명이 해설과 정확히 일치합니다.

출처: game_dev/cpp/chapter7/2025-10-28-cpp_7_41.md


Q2. 🟡 보통 — ⬜ 0/10

문제: 빈칸을 채우시오. (대소문자는 구분하지 않습니다.)

SQL Server에서 테이블의 필드 데이터 형식을 고를 때의 지침이다.

  • 한글을 저장할 필드에는 varchar가 아니라 ① ___를 사용한다. (varchar는 한글이 깨진다)
  • 금융 계산처럼 정확한 값이 필요하면 오차가 있는 float 대신 ② ___를 사용한다.
  • 구형 타입 text는 Deprecated 되었으므로 대신 ③ ___를 사용한다.
  • null은 값 자체가 없음을 뜻하며, 0이나 ""(빈 문자열)과 ④ ___하기 위해 존재한다.

📝 내 답: (미응답)

정답:

① nvarchar ② decimal ③ varchar(MAX) ④ 구별 (구분)

해설:

한글처럼 유니코드가 필요한 문자열은 nvarchar에 저장합니다. 금융 계산은 정확한 고정 소수점인 decimal을 쓰고(float은 근사값이라 오차가 있음), Deprecated된 text는 varchar(MAX)로 대체합니다. null은 “값이 없음”이라는 의미를 0, 빈 문자열과 구별하기 위해 존재합니다.

🔍 피드백: 미응답입니다. 꼭 기억할 핵심은 네 가지입니다. 한글은 nvarchar, 정확한 수치는 decimal, text의 대체는 varchar(MAX), null은 0·빈 문자열과 구별하려고 존재한다는 점입니다.

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


Q3. 🔴 어려움 — ❌ 0/10

문제: swap을 직접 만든 클래스에 대해 효율적으로 지원하는 방법에 대해 답하시오.

표준 std::swap은 아래와 같이 구현되어 있다. 그리고 Widget은 실제 데이터를 담은 WidgetImpl 객체를 포인터 pImpl로 가리키는 pimpl 구조의 클래스이다.

namespace std {
    template<typename T>
    void swap(T& a, T& b) {
        T temp(a);
        a = b;
        b = temp;
    }
}

class Widget {
    ...
private:
    WidgetImpl* pImpl;
};

(1) Widget 두 객체를 std::swap으로 맞바꾸면 왜 비효율적인가? 몇 개의 무엇이 복사되는지 쓰시오.

(2) Widget이 클래스 템플릿이 아닌 경우, std::swap<Widget>을 완전 특수화하여 본문에서 swap(a.pImpl, b.pImpl);을 직접 호출하면 컴파일되지 않는다. 이유는 무엇이며, 어떻게 해결하는가?

(3) Widget<T>가 클래스 템플릿인 경우에는 std 네임스페이스에서 다음 두 방법을 쓸 수 없다. 각각 왜 안 되는지 설명하고, 올바른 해결 방법을 쓰시오.

  • 가. template<typename T> void swap<Widget<T>>(Widget<T>&, Widget<T>&) 처럼 std::swap을 부분 특수화
  • 나. std 안에 template<typename T> void swap(Widget<T>&, Widget<T>&)를 새로 오버로딩

(4) 사용자 코드에서 std::swap(obj1, obj2);로 호출하면 안 되는 이유와, 올바른 호출 방식을 쓰시오.

📝 내 풀이:

너무 헷갈려서 레퍼런스를 살펴보고 답안을 적어 0점 처리입니다.

(1) Widget을 맞바꾸기 위해 불필요한 복사 연산이 생긴다. 3개의 Widget과 3개의 WidgetImpl

(2) pImpl이 private 멤버이기 때문이다. swap의 완전 템플릿 특수화 버전이 Widget 클래스의 swap 멤버 함수를 호출하도록 짠다.

(3) 가: 함수 템플릿에 대해 부분 특수화를 허용하지 않는다. 나: std 네임스페이스에 오버로딩하는 것은 허용되지 않는다. 사용자 네임스페이스에 swap의 부분 템플릿 특수화 버전을 선언하고, Widget 클래스의 swap 멤버 함수를 호출하도록 한다.

(4) using std::swap; swap(obj1, obj2);

정답:

(1) std::swap은 T temp(a); a = b; b = temp;로 복사가 총 3번 일어납니다. pimpl 타입은 실제로는 pImpl 포인터만 맞바꾸면 되는데도, Widget 객체 3개와 WidgetImpl 객체 3개를 복사하게 되어 매우 비효율적입니다.

(2) pImpl이 private 멤버라서 std 안의 특수화 함수에서 접근할 수 없기 때문입니다. Widget에 public 멤버 함수 swap(Widget& other)를 추가해 그 안에서 using std::swap; swap(pImpl, other.pImpl);을 수행하고, std::swap<Widget> 특수화는 a.swap(b);만 호출하게 합니다.

(3)

  • 가: C++은 클래스 템플릿의 부분 특수화는 허용하지만 함수 템플릿의 부분 특수화는 허용하지 않기 때문에 컴파일 에러가 납니다. (std::swap은 함수 템플릿)
  • 나: 컴파일은 되지만, std 네임스페이스에는 완전 특수화만 가능하고 새 템플릿을 추가하는 것은 표준 위반이라 결과가 미정의(UB)입니다.
  • 해결: Widget/WidgetImpl이 있는 자신의 네임스페이스(예: WidgetStuff) 안에 멤버 swap을 호출하는 비멤버 swap 오버로딩을 만듭니다. 그러면 인자 기반 탐색(ADL)으로 swap(a, b) 호출 시 이 함수가 선택됩니다.

(4) std::swap(...)처럼 네임스페이스를 명시하면 항상 std::swap만 호출되어 사용자 정의 swap을 쓸 수 없습니다. 반드시 using std::swap;을 선언한 뒤 swap(obj1, obj2);로 호출해야 하며, 이러면 T 전용 swap → std의 T 특수화 → 일반 std::swap 순으로 탐색됩니다.

🔍 피드백: 레퍼런스를 보고 푼 답안이라 직접 선언하신 대로 0점 처리했습니다. 내용만 보면 (1)·(2)·(3)은 정확했습니다. (3)의 해결책은 “비멤버 swap 오버로딩”이라고 쓰는 편이 더 정확합니다. 사용자 네임스페이스에 두는 것은 맞지만, 함수 템플릿이라 부분 특수화가 아니라 오버로딩입니다. (4)는 호출 형태는 맞았으나 std::swap(...)이 왜 안 되는지(사용자 정의 swap을 무시함)가 빠졌습니다. 레퍼런스 없이 다시 풀 수 있는지 오답노트로 확인해 보세요.

출처: game_dev/cpp/chapter4/2025-07-02-cpp_4_25.md


Q4. 🟡 보통 — ❌ 0/10

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

std::unordered_map<std::string, int> m;의 원소는 실제로 std::pair<const std::string, int> 타입이다. 따라서 for (const std::pair<std::string, int>& p : m)처럼 const가 빠진 타입으로 순회해도 원소를 복사 없이 그대로 참조하므로, for (const auto& p : m)으로 바꿔도 성능 차이는 없다.

📝 내 답: O. 잘은 모르겠지만, 그럴 것으로 예상된다

정답: X

해설:

원소 타입은 pair<const string, int>이므로 pair<string, int>와 다른 타입입니다. 컴파일러는 이를 변환하기 위해 매 반복마다 pair<string, int> 임시 객체를 만들고 원소를 복사한 뒤 p를 그 임시 객체에 묶고, 루프 끝에서 파괴합니다. 즉 의도치 않은 변환과 복사가 일어나 비효율적입니다. const auto& p를 쓰면 원소를 그대로 참조하므로 성능이 개선됩니다.

🔍 피드백: 판단이 틀렸고 근거도 없어 0점입니다. 핵심은 두 가지입니다. 참조는 같은 타입의 객체에만 묶이는데, pair<const string, int>와 pair<string, int>는 서로 다른 타입입니다. 그래서 매 반복마다 임시 객체가 생겨 복사가 일어납니다. 문제에서 “복사 없이 참조한다”는 부분이 함정이었습니다.

출처: game_dev/moderncpp/2/2026-04-07-moderncpp_2_5.md


Q5. 🟡 보통 — ⭕ 10/10

문제: 다음 다중 상속 코드에 대한 설명으로 가장 올바른 것은?

class BorrowableItem {
public:
    void checkOut();           // 도서관에서 체크아웃
};

class ElectronicGadget {
private:
    bool checkOut() const;     // 테스트 실시 후 성공 여부 반환
};

class MP3Player : public BorrowableItem, public ElectronicGadget { };

MP3Player mp;
mp.checkOut();
  • A. ElectronicGadget::checkOut이 private이므로 접근할 수 없는 후보에서 제외되어, BorrowableItem::checkOut이 자동으로 호출된다.
  • B. 컴파일러는 최적 일치 함수를 먼저 찾은 뒤에 접근 가능성을 점검하므로 public/private 차이와 무관하게 모호성 에러가 나며, mp.BorrowableItem::checkOut();처럼 클래스를 명시해 해결한다.
  • C. 마름모꼴 상속에서는 데이터 중복을 막기 위해 가상 상속이 필요하며, 가상 상속은 비용이 없으므로 항상 사용하는 것이 좋다.
  • D. 가상 기본 클래스에는 데이터를 넣는 편이 초기화와 대입의 복잡성을 줄여 준다.

📝 내 선택: B (A·C·D 오답 근거도 함께 작성)

정답: B

해설:

  • A (✗): 접근 가능성은 최적 일치 함수를 고른 뒤에 점검하므로, private이라도 후보에서 제외되지 않고 모호성 에러가 납니다.
  • B (✓): public/private는 모호성 판단에 쓰이지 않으며, mp.BorrowableItem::checkOut()처럼 직접 클래스를 명시해 해결합니다.
  • C (✗): 가상 상속은 객체 크기 증가, 접근 속도 저하, 초기화 복잡도 증가라는 비용이 큽니다. 쓸 필요가 없다면 비가상 상속을 쓰는 것이 좋습니다.
  • D (✗): 반대입니다. 가상 기본 클래스에는 데이터를 넣지 않는 것이 초기화·대입의 복잡성을 줄입니다.

🔍 피드백: 정답입니다. C(가상 상속의 비용)와 D(데이터를 넣으면 복잡성이 늘어남)는 정확히 짚었습니다. A는 “private 상속”을 언급하셨는데, 이 문제의 핵심은 상속 방식이 아니라 멤버 함수의 접근 제어는 이름 해석(모호성 판단) 이후에 점검된다는 점입니다. 이 구분을 같이 기억해 두세요.

출처: game_dev/cpp/chapter6/2025-10-28-cpp_6_40.md


Q6. 🟡 보통 · 📌 오답노트 — 🔺 7.5/10

문제: 게임 서버에서 여러 스레드가 공유 데이터를 다룰 때의 문제에 대해 답하시오.

(1) 전역 변수 gold의 값이 100일 때, 스레드 A는 gold += 50;을, 스레드 B는 gold += 30;을 동시에 실행한다. 이 연산은 기계어 수준에서 t = gold → t = t + y → gold = t 세 단계로 쪼개진다. 실행이 어떻게 섞이느냐에 따라 gold가 최종적으로 가질 수 있는 값을 모두 쓰고, 각각 어떤 경우인지 설명하시오.

(2) 소수를 찾는 4개의 스레드가 공유 변수 num을 while (num <= 1000000) { ...; num++; }으로 함께 증가시키면, num++ 때문에 어떤 현상이 생길 수 있는가? 두 가지를 쓰시오.

(3) 4개의 스레드가 공유하는 Array<int> primes는 내부에 “배열을 가리키는 포인터”와 “배열의 크기 정보”를 가지고 있다. 여러 스레드가 동시에 primes.Add()를 호출하다가 공간이 부족해 내부에서 메모리 재할당이 일어나면 어떤 일이 벌어질 수 있는지, 포인터의 변화를 중심으로 설명하시오.

(4) (2)와 (3)의 문제를 해결하려면 보장해야 하는 두 가지 성질의 이름을 쓰고, 각각 (2)·(3) 중 어느 문제와 대응되는지 한 줄로 설명하시오. 또한 이를 가능하게 하는 동기화 기법 세 가지를 쓰시오.

출처: server/game_server/1/2025-05-30-game_server_1_5.md 오답노트: wn-2de515c0 · 2026-09-09 최초 오답 · 이번이 3번째 복습

📝 내 풀이:

(1) 130, 150, 180. 각 기계어 명령은 섞일수 있으며, 여기서 결과가 나뉘는 핵심은 gold = t 실행의 순서로 결정된다(자세한 내용은 너무 길게 써야해 생략한다)

(2) 1. num++의 기계어 수준에선 3단계로 쪼개지기에 num이 증가되지 않는 경우가 생긴다. 2: 나머지 하나는 모르겠다

(3) 재할당이 일어났을때 배열을 가리키는 포인터가 이동했을수도 있다. 댕글링 포인터 문제로 인해 프로그램이 종료될수 있다고

(4) 원자성, 일관성. 뮤텍스, 모니터, 세마포어

정답:

(1) 가능한 값은 180, 150, 130입니다.

  • 180: 한 스레드가 읽기-더하기-쓰기를 끝낸 뒤 다른 스레드가 실행되어 두 덧셈이 모두 반영된 정상 경우입니다.
  • 150: 두 스레드가 모두 gold = 100을 읽은 상태에서, A(100 + 50)가 마지막에 쓰는 경우입니다. B의 +30이 사라집니다.
  • 130: 두 스레드가 모두 100을 읽은 상태에서, B(100 + 30)가 마지막에 쓰는 경우입니다. A의 +50이 사라집니다.

(2) 여러 스레드가 동시에 num을 읽고 증가시키므로 같은 값을 여러 번 처리하거나 값을 건너뛰는 일이 생길 수 있습니다. (r = num; r = r + 1; num = r;로 쪼개지기 때문)

(3) 공간이 부족하면 더 큰 메모리를 재할당하고 포인터가 새 위치를 가리키도록 바뀝니다. 이를 모르는 다른 스레드가 예전 포인터로 접근하면 이미 해제된 메모리를 건드리게 되어 충돌(크래시)이 발생합니다. 또한 포인터와 크기가 서로 어긋난 중간 상태가 노출될 수 있습니다.

(4)

  • 원자성(Atomicity): 하나의 작업 단위가 쪼개지지 않게 하는 성질 → (2)의 num++, primes.Add() 같은 연산이 대응됩니다.
  • 일관성(Consistency): 배열 포인터와 크기처럼 여러 멤버가 항상 서로 어울리는 상태여야 한다는 성질 → (3)의 재할당 중간에 끼어들지 못하게 보호해야 하는 문제가 대응됩니다.
  • 동기화(Synchronization) 기법: 임계 영역(Critical Section), 뮤텍스(Mutex), 잠금(Lock)

🔍 피드백: 소문항별 2.5점씩으로 채점했습니다.

  • (1) 2.0/2.5 — 값 세 개는 모두 맞았습니다. 다만 어떤 경우에 어떤 값이 나오는지 설명을 생략하셨습니다. 150은 A가 마지막에 쓰는 경우, 130은 B가 마지막에 쓰는 경우입니다.
  • (2) 1.5/2.5 — “증가가 반영되지 않는 경우”는 “값을 건너뛰는/덮어쓰는” 현상에 해당해 인정했습니다. 두 번째인 “같은 값을 여러 스레드가 중복 처리”가 빠졌습니다. 두 스레드가 같은 num을 읽으면 같은 수를 두 번 검사합니다.
  • (3) 2.5/2.5 — 포인터가 새 위치로 옮겨졌는데 다른 스레드가 옛 포인터를 쓰면 해제된 메모리를 건드려 크래시가 난다는 핵심을 맞혔습니다.
  • (4) 1.5/2.5 — 성질 이름과 동기화 기법은 맞았습니다. 모니터와 세마포어도 동기화 기법으로 인정했습니다. 다만 원자성↔(2), 일관성↔(3)의 대응을 설명하지 않았습니다.

만점이 아니므로 오답노트에서는 다시 처음(stage 0)부터 복습하게 됩니다.

출처: server/game_server/1/2025-05-30-game_server_1_5.md 오답노트: wn-2de515c0 · 2026-09-09 최초 오답 · 이번이 3번째 복습


📌 복습 포인트

  • Q2 SQL Server 자료형 — 한글은 nvarchar, 정확한 수치는 decimal, text 대체는 varchar(MAX), null의 의미를 다시 봅니다. → server/game_server/7/2026-03-02-game_server_7_4.md
  • Q3 swap 특수화/오버로딩 — 레퍼런스 없이 (1)~(4)를 다시 풀어 봅니다. 특히 std::swap(...) 직접 호출이 왜 안 되는지를 확인합니다. → game_dev/cpp/chapter4/2025-07-02-cpp_4_25.md
  • Q4 pair<const K, V> 순회 — 타입이 다르면 참조가 아니라 임시 객체 복사가 일어난다는 점을 다시 봅니다. → game_dev/moderncpp/2/2026-04-07-moderncpp_2_5.md
  • Q6 동기화 문제 — num++의 두 번째 현상(같은 값 중복 처리)과 원자성↔num++, 일관성↔재할당 대응을 다시 봅니다. → server/game_server/1/2025-05-30-game_server_1_5.md

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

댓글남기기