[말랑 퀴즈] 26/08/22 문제

게시:     수정

카테고리:

태그:

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

말랑말랑 퀴즈 📝

날짜: 2026-08-22 문제 수: 5문제


Q1. 🟡 보통

WidgetBitmap 객체를 힙에 들고 있는 클래스이고, pb는 그것을 가리키는 포인터 멤버다. 아래는 이 클래스의 복사 대입 연산자다.

class Widget {
    Bitmap* pb;   // 힙에 할당된 Bitmap을 가리킨다
    ...
};

Widget& Widget::operator=(const Widget& rhs) {
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

(1) 이 코드는 두 가지 상황에서 위험하다. 각각이 무엇이며 왜 위험한지 설명하시오.

(2) 함수 맨 앞에 아래 한 줄을 추가하는 방식이 있다. 이 방식의 한계는 무엇인가?

(3) 위 한 줄 없이도 (1)의 두 상황을 모두 안전하게 만드는 방법이 있다. 고친 코드를 작성하고, 왜 안전해지는지 설명하시오.

출처: game_dev/cpp/chapter2/2025-06-07-cpp_2_11.md

📝 내 풀이:

(1)

  1. delete pb; 에서 예외가 발생하는 상황
  2. new 연산자에서 예외가 발생하는 상황

기존 pb를 백업해두지 않은 상태에서 pb를 삭제하고 대입하고 있으며, new 연산자에서 예외가 발생하면 메모리 누수가 발생할 수 있기 때문이다.

(2)

if (this == &rhs) return *this;

어디까지 동일한 객체라고 판단할 것인지 경계가 모호하다. (일부 변수만 같으면 같은가? 아니면 전체가 같아야 하는가?)

(3)

Widget& Widget::operator=(const Widget& rhs) {
    Bitmap origin = pb;
    Bitmap value = rhs;

    try {
        swap(pb, value);
    }
    catch(const std::exception e&) {
        pb = origin;
    }

    return *this;
}

swap 말고도 객체 생성 시 두개의 코드 줄로 나누는 방식도 있었는데 정확히 기억은 안난다. 안전해지는 이유는 기존 데이터 수정은 없애, 예외 처리를 하게 두는 것이다.


Q2. 🟡 보통

아래 코드에서 Airplane::fly단순 가상 함수(= 0이 붙지 않은 virtual 함수)이고, 기본 구현이 제공되어 있다.

class Airplane {
public:
    virtual void fly(const Airport& destination);   // 단순 가상 함수
    ...
};

// 목적지로 비행기를 날려보내는 기본 구현
void Airplane::fly(const Airport& destination) { ... }

class ModelA : public Airplane { ... };   // 기본 구현을 그대로 씀
class ModelB : public Airplane { ... };   // 기본 구현을 그대로 씀

그런데 나중에 추가된 ModelC비행 방식이 완전히 다른데도 fly 재정의를 빼먹었다.

class ModelC : public Airplane {
    ...        // fly를 재정의하지 않음
};

int main() {
    Airport PDX(...);
    Airplane* pa = new ModelC;
    pa->fly(PDX);          // ← 무슨 일이 벌어지는가?
}

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

  • A. ModelCfly를 재정의하지 않았으므로 new ModelC 지점에서 컴파일 에러가 발생한다.
  • B. 단순 가상 함수는 인터페이스와 함께 기본 구현까지 물려주기 때문에 재정의를 강제할 방법이 없고, ModelC는 자신에게 맞지 않는 Airplane::fly를 그대로 쓰게 된다. fly를 순수 가상 함수로 바꾸고 기본 동작을 defaultFly 같은 별도 함수로 분리하면 재정의를 강제할 수 있다.
  • C. fly를 비가상 함수로 바꾸면 파생 클래스마다 각자의 구현을 제공하도록 강제할 수 있으므로 이 문제가 해결된다.
  • D. Airplane은 가상 함수를 가지고 있으므로 추상 클래스이며, 따라서 Airplane* pa = new ModelC; 자체가 컴파일되지 않는다.

출처: game_dev/cpp/chapter6/2025-10-20-cpp_6_34.md

📝 내 선택: B


Q3. 🟢 쉬움

빈칸을 채우시오.

auto의 형식 연역은 대체로 템플릿 형식 연역과 똑같이 작동하지만, 딱 한 가지 예외가 있다. 중괄호 초기치를 만났을 때다.

int  x1 = 27;        // 형식 = int
int  x3 = { 27 };    // 형식 = int

auto y1 = 27;        // 형식 = ① ___
auto y3 = { 27 };    // 형식 = ② ___

auto 형식 연역은 중괄호 초기치가 ②를 나타낸다고 가정하지만, 템플릿 형식 연역은 그렇게 가정하지 않는다. 그래서 아래 두 경우에는 auto 형식 연역이 아니라 ③ ___ 형식 연역 규칙이 적용되어 컴파일이 거부된다.

auto createInitList() {
    return { 1, 2, 3 };                                   // ⚠️ 오류
}

std::vector<int> v;
auto resetV = [&v](const auto& newValue) { v = newValue; };
resetV({ 1, 2, 3 });                                      // ⚠️ 오류

출처: game_dev/moderncpp/1/2026-04-03-moderncpp_1_2.md

📝 내 답: int, vector, 템플릿


Q4. 🔴 어려움

UE5의 CharacterMovementComponent를 확장해 Sprint를 이동 패킷의 FLAG_Custom_0 비트에 실었다. 클라는 매 프레임 FSavedMove(이동 스냅샷)를 만들고, 엔진은 보류 중인 Move 하나와 새로 만든 Move 하나를 합칠 수 있는지 CanCombineWith로 검사한다. 합쳐지면 두 프레임이 하나의 압축 플래그로 전송된다.

속도 계산은 이렇게 되어 있다.

float UEPCharacterMovement::GetMaxSpeed() const
{
    if (bWantsToSprint && IsMovingOnGround()) return SprintSpeed;   // 650
    if (bWantsToAim) return AimSpeed;                               // 200
    return Super::GetMaxSpeed();                                    // 600
}

엔진의 부모 구현은 이미 두 Move의 MaxSpeed를 비교해 다르면 합치지 않는다.

// FSavedMove_Character::CanCombineWith
if (!FMath::IsNearlyEqual(MaxSpeed, NewMove->MaxSpeed, MaxSpeedThresholdCombine))
{
    return false;
}

그런데도 아래처럼 오버라이드를 추가했다.

bool FSavedMove_EPCharacter::CanCombineWith(const FSavedMovePtr& NewMove,
                                            ACharacter* Character, float MaxDelta) const
{
    FSavedMove_EPCharacter* Other = static_cast<FSavedMove_EPCharacter*>(NewMove.Get());
    if (bSavedWantsToSprint != Other->bSavedWantsToSprint) return false;
    if (bSavedWantsToAim    != Other->bSavedWantsToAim)    return false;
    return FSavedMove_Character::CanCombineWith(NewMove, Character, MaxDelta);
}

(1) 부모가 이미 MaxSpeed를 비교하고 있는데도 이 오버라이드가 필요한 이유를 설명하시오. 어떤 상황에서 부모 검사만으로는 부족해지는가?

(2) 그 상황에서 오버라이드가 없으면 최종적으로 무슨 문제가 발생하는지, 순서대로 설명하시오.

출처: game_dev/devlog/2026-02-10-EP_Replication-1.md

📝 내 풀이:

(1) Sprint/Aim 같은 플래그는 합쳐질 수 없기 때문이다. 하나만 달라도 합치는게 불가능하다.
예를 들어 합쳐지는 범위가 0.5s라 치자. 0s에 sprint==1, 0.3s에 sprint==0가 되면 합칠 수 없다.

(2) 플래그가 무시되는 상황이 발생한다.

  1. 합쳐지는 범위는 0.5s
  2. 0s 걷기
  3. 0.1s 달리기
  4. 0.3s 걷기

이 경우 달리기가 취소된다.


Q5. 🔴 어려움

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

typedef는 그 자체로는 템플릿화할 수 없지만, 아래처럼 구조체로 감싸면 템플릿화할 수 있다.

template<typename T>
struct MyAllocList {
    typedef std::list<T, MyAlloc<T>> type;
};

따라서 클래스 템플릿 안에서도 아래처럼 쓸 수 있으며, 별칭 템플릿(template<typename T> using MyAllocList = std::list<T, MyAlloc<T>>;)을 쓰는 것과 사용법상 차이가 없다.

template<typename T>
class Widget {
private:
    MyAllocList<T>::type list;
};

출처: game_dev/moderncpp/3/2026-04-18-moderncpp_3_9.md

📝 내 답: X. 아니었던 것 같긴한데 기억이 잘 안난다.

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

댓글남기기