36. 거래 확정(confirm) 토글과 제안 변경 경합 (미끼 교환 / bait-and-switch, C#)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: MMORPG 1:1 직거래(트레이드) 창. 두 플레이어가 각자 아이템을 창에 올리고,
// 서로 "확정(Confirm)" 버튼을 누른다. 양쪽이 모두 확정하면 서버가 거래를 성사시켜
// 아이템을 맞바꾼다. 거래 내용은 확정 전까지 언제든 바꿀 수 있다(품목 추가/교체/제거).
//
// 요구사항:
// - SetOffer: 자기 쪽 제안 목록을 통째로 교체한다(거래 진행 중 내용 변경 허용).
// - Confirm: 자기 쪽을 확정 상태로 만든다.
// - TryExecute: 양쪽이 확정이면 제안 목록대로 소유권을 맞바꾼다.
// - 한 거래 세션에는 두 플레이어의 요청이 서로 다른 처리 스레드에서 들어올 수 있다.
//
// 과제: (A)(B) 지점을 검토하라. 특정 요청 순서·타이밍에서 한쪽이 "본 것과 다른" 거래를
// 성사시킬 수 있다. 어떤 조건에서 그렇게 되는지 밝히고 고쳐라.
using System.Collections.Generic;
public sealed class Player
{
public readonly long Id;
public readonly HashSet<long> Inventory = new(); // 보유 아이템 인스턴스 ID
public Player(long id) { Id = id; }
}
public sealed class TradeSide
{
public readonly Player Owner;
public List<long> Offer = new(); // 이 사람이 내놓은 아이템 인스턴스 ID
public bool Confirmed = false;
public TradeSide(Player p) { Owner = p; }
}
public sealed class TradeSession
{
private readonly TradeSide _a, _b;
public TradeSession(Player a, Player b) { _a = new TradeSide(a); _b = new TradeSide(b); }
private TradeSide SideOf(long playerId) => _a.Owner.Id == playerId ? _a : _b;
// 플레이어가 자기 제안 목록을 교체
public void SetOffer(long playerId, List<long> items)
{
var side = SideOf(playerId);
side.Offer = items; // (A)
}
public void Confirm(long playerId)
{
SideOf(playerId).Confirmed = true;
}
// 양쪽 확정 시 소유권 맞바꿈
public bool TryExecute()
{
if (!_a.Confirmed || !_b.Confirmed) return false; // (B)
foreach (var id in _a.Offer) { _a.Owner.Inventory.Remove(id); _b.Owner.Inventory.Add(id); }
foreach (var id in _b.Offer) { _b.Owner.Inventory.Remove(id); _a.Owner.Inventory.Add(id); }
return true;
}
} 결함 코드 · C++
// 시나리오: MMORPG 1:1 직거래(트레이드) 창(C++ 서버). 두 플레이어가 각자 아이템을 창에
// 올리고, 서로 "확정(confirm)" 한다. 양쪽이 모두 확정하면 서버가 소유권을 맞바꾼다.
// 거래 내용은 확정 전까지 언제든 바꿀 수 있다(품목 추가/교체/제거).
//
// 요구사항:
// - setOffer: 자기 쪽 제안 목록을 통째로 교체한다(거래 진행 중 변경 허용).
// - confirm : 자기 쪽을 확정 상태로 만든다.
// - tryExecute: 양쪽이 확정이면 제안 목록대로 소유권을 맞바꾼다.
// - 두 플레이어의 요청은 서로 다른 워커 스레드에서 같은 세션에 들어올 수 있다.
//
// 과제: (A)(B) 지점을 검토하라. 특정 요청 순서·타이밍에서 한쪽이 "본 것과 다른" 거래를
// 성사시키거나 집계가 깨질 수 있다. 어떤 조건에서 그런지 밝히고 고쳐라.
#include <cstdint>
#include <vector>
#include <unordered_set>
struct Player {
uint64_t id;
std::unordered_set<uint64_t> inventory; // 보유 아이템 인스턴스 ID
};
struct TradeSide {
Player* owner = nullptr;
std::vector<uint64_t> offer; // 내놓은 아이템 인스턴스 ID
bool confirmed = false;
};
class TradeSession {
public:
TradeSession(Player* a, Player* b) { a_.owner = a; b_.owner = b; }
void setOffer(uint64_t playerId, const std::vector<uint64_t>& items) {
TradeSide& s = sideOf(playerId);
s.offer = items; // (A)
}
void confirm(uint64_t playerId) {
sideOf(playerId).confirmed = true;
}
bool tryExecute() {
if (!a_.confirmed || !b_.confirmed) return false; // (B)
for (uint64_t id : a_.offer) { a_.owner->inventory.erase(id); b_.owner->inventory.insert(id); }
for (uint64_t id : b_.offer) { b_.owner->inventory.erase(id); a_.owner->inventory.insert(id); }
return true;
}
private:
TradeSide& sideOf(uint64_t pid) { return a_.owner->id == pid ? a_ : b_; }
TradeSide a_, b_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.