32. 길드 해산과 자금고/창고 접근의 수명·자금 경합
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 길드 시스템. 길드는 공용 자금고(Funds)와 창고를 가진다.
// - 길드장이 길드를 해산(Disband)하면: 남은 자금을 길드장에게 환급하고,
// 창고를 정리하고, 길드를 레지스트리에서 제거한다.
// - 길드원/임원은 자금고에서 출금(Withdraw)할 수 있다.
// 해산과 출금은 서로 다른 요청 워커 스레드에서 같은 길드에 동시에 들어올 수 있다.
//
// 요구사항:
// - 길드 자금은 총합이 보존돼야 한다. 같은 자금이 해산 환급과 출금으로 "두 번"
// 빠져나가면 안 된다(자금 복제/음수 잔액 금지).
// - 해산이 끝난(제거된) 길드에 대한 출금은 거부돼야 한다(좀비 접근 금지).
//
// 과제: 해산과 출금이 동시에 일어날 때 이 코드가 어떻게 잘못 동작하는지
// (A)~(C) 지점을 중심으로 진단하고, 자금 보존과 수명 정합성을 보장하도록
// 수정하라.
using System.Collections.Generic;
public sealed class Guild
{
public long Id;
public long LeaderId;
public long Funds;
public List<int> Storage = new();
}
public sealed class GuildService
{
private readonly Dictionary<long, Guild> _guilds = new();
public void Register(Guild g) => _guilds[g.Id] = g;
public bool Withdraw(long guildId, long memberId, long amount)
{
if (!_guilds.TryGetValue(guildId, out var g)) // (A) 길드 존재 확인
return false;
if (g.Funds < amount) // (B) 잔액 확인
return false;
g.Funds -= amount; // (B) 차감
Bank.Give(memberId, amount); // 지급
return true;
}
public void Disband(long guildId, long leaderId)
{
if (!_guilds.TryGetValue(guildId, out var g)) return;
if (g.LeaderId != leaderId) return;
Bank.Give(g.LeaderId, g.Funds); // (C) 남은 자금 환급
g.Funds = 0;
g.Storage.Clear(); // 창고 정리
_guilds.Remove(guildId); // (C) 레지스트리에서 제거
}
}
public static class Bank { public static void Give(long playerId, long amount) {} } 결함 코드 · C++
// 시나리오: 길드 시스템 (C++). 길드는 공용 자금고(funds)와 창고를 가진다.
// - 길드장이 길드를 해산(disband)하면: 남은 자금을 길드장에게 환급하고,
// 창고를 정리하고, 길드를 레지스트리에서 제거한 뒤 객체를 해제한다.
// - 길드원/임원은 자금고에서 출금(withdraw)할 수 있다.
// 해산과 출금은 서로 다른 워커 스레드에서 같은 길드에 동시에 들어올 수 있다.
//
// 요구사항:
// - 길드 자금 총합이 보존돼야 한다. 같은 자금이 해산 환급과 출금으로 두 번
// 빠져나가면 안 된다(자금 복제/음수 잔액 금지).
// - 해산된(제거·해제된) 길드에 대한 출금은 거부돼야 하며, 해제된 객체를
// 건드려서는 안 된다.
//
// 과제: 해산과 출금이 동시에 일어날 때 이 코드가 어떻게 잘못 동작하는지
// (A)~(C) 지점을 중심으로 진단하고, 자금 보존과 수명 정합성을 보장하도록
// 수정하라.
#include <cstdint>
#include <unordered_map>
#include <vector>
namespace Bank { void give(int64_t playerId, int64_t amount); }
struct Guild {
int64_t id;
int64_t leaderId;
int64_t funds;
std::vector<int> storage;
};
class GuildService {
public:
void registerGuild(Guild* g) { guilds_[g->id] = g; }
bool withdraw(int64_t guildId, int64_t memberId, int64_t amount) {
auto it = guilds_.find(guildId); // (A) 길드 조회
if (it == guilds_.end()) return false;
Guild* g = it->second;
if (g->funds < amount) return false; // (B) 잔액 확인
g->funds -= amount; // (B) 차감
Bank::give(memberId, amount); // 지급
return true;
}
void disband(int64_t guildId, int64_t leaderId) {
auto it = guilds_.find(guildId);
if (it == guilds_.end()) return;
Guild* g = it->second;
if (g->leaderId != leaderId) return;
Bank::give(g->leaderId, g->funds); // (C) 남은 자금 환급
g->funds = 0;
guilds_.erase(it); // (C) 레지스트리에서 제거
delete g; // (C) 객체 해제
}
private:
std::unordered_map<int64_t, Guild*> guilds_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.