C ++에서 게임 오브젝트가 실수로 자신을 삭제하는 것을 피하는 방법


20

내 게임에 플레이어를 폭발시킬 수있는 몬스터가 있다고 가정 해 봅시다. 이 괴물의 이름을 무작위로 선택합시다 : 크리퍼. 따라서 Creeper클래스에는 다음과 같은 메소드가 있습니다.

void Creeper::kamikaze() {
    EventSystem::postEvent(ENTITY_DEATH, this);

    Explosion* e = new Explosion;
    e->setLocation(this->location());
    this->world->addEntity(e);
}

이벤트는 대기하지 않고 즉시 전달됩니다. 이로 인해 Creeper호출 내부의 어딘가 에서 객체가 삭제됩니다 postEvent. 이 같은:

void World::handleEvent(int type, void* context) {
    if(type == ENTITY_DEATH){
        Entity* ent = dynamic_cast<Entity*>(context);
        removeEntity(ent);
        delete ent;
    }
}

메소드가 여전히 실행되는 Creeper동안 오브젝트가 삭제 되므로 kamikaze액세스하려고 하면 오브젝트가 충돌합니다 this->location().

한 가지 해결책은 이벤트를 버퍼에 대기시키고 나중에 전달하는 것입니다. 이것이 C ++ 게임의 일반적인 솔루션입니까? 그것은 약간의 해킹처럼 느껴지지만 다른 메모리 관리 방법을 가진 다른 언어에 대한 나의 경험 때문일 수 있습니다.

C ++에서 객체가 실수로 메소드 중 하나에서 자신을 삭제하는이 문제에 대한 더 나은 일반적인 해결책이 있습니까?


6
어, 시작이 아닌 kamikaze 메서드의 끝에서 postEvent를 호출하는 것은 어떻습니까?
Hackworth

@Hackworth는이 특정 예제에서 작동하지만보다 일반적인 솔루션을 찾고 있습니다. 어디서나 이벤트를 게시하고 충돌을 두려워하지 않기를 원합니다.
Tom Dalling

autoreleaseObjective-C 의 구현을 살펴보면 , "약간"까지 삭제가 보류됩니다.
크리스 버트 브라운

답변:


40

삭제하지 마십시오 this

암묵적으로도.

-이제까지-

멤버 함수 중 하나가 여전히 스택에있는 동안 오브젝트를 삭제하면 문제가 발생합니다. ( "실수로"여부)이 일어나는 결과는 모든 코드 아키텍처를 객관적으로 나쁜 이며, 위험 ,되어야한다 즉시 리팩토링 . 이 경우, 몬스터가 'World :: handleEvent'를 호출 할 수있게 된다면 어떤 상황에서도 해당 함수 안의 몬스터를 삭제하지 마십시오!

(이 특정 상황에서, 나의 일반적인 접근 방식은 몬스터가 자체에 '죽은'깃발을 설정하고 월드 객체 또는 이와 비슷한 것을 프레임마다 한 번씩 '죽은'깃발을 테스트하여 그 객체를 제거하는 것입니다. 월드의 오브젝트 목록에서 삭제하거나 몬스터 풀 또는 그 밖의 적절한 것에 반환합니다. 현재 월드는 삭제에 대한 알림을 전송하므로 월드의 다른 오브젝트는 몬스터가 현재 존재하는 객체가 없다는 것을 알 때 세계는 안전한 시간 에이 작업을 수행하므로 스택이 특정 지점으로 풀리는 것에 대해 걱정할 필요가 없습니다. 'this'포인터는 사용 가능한 메모리를 가리 킵니다.)


6
괄호 안에있는 답변의 후반부가 도움이되었습니다. 플래그를 폴링하는 것이 좋습니다. 그러나 나는 그것을하는 것이 좋든 나쁘지 않든 그것을 피하는 방법을 묻고있었습니다. 질문에 " 실수로 X를 피하는 방법은 무엇입니까? " 라고 물으면 대답이 " 실수로 X를 절대로하지 마십시오 "라고 굵은 글씨로 표시 한 경우 실제로 질문에 대답하지 않습니다.
Tom Dalling

7
나는 내 대답의 전반부에 작성한 의견 뒤에 서서, 그들이 원래의 표현대로 질문에 완전히 대답한다고 생각합니다. 여기서 반복 할 중요한 점은 객체가 자체적으로 삭제되지 않는다는 것입니다. 이제까지 . 다른 사람에게 전화를 걸어 삭제하지 않습니다. 이제까지 . 대신, 객체 외부에 객체를 소유하고 객체를 파괴해야 할 때를 알아내는 다른 무언가가 있어야합니다. 이것은 "괴물이 죽었을 때"가 아닙니다. 이것은 항상 모든 곳에서 모든 C ++ 코드에 적용됩니다. 예외 없음.
트레버 파월

3
@TrevorPowell 나는 당신이 틀렸다는 것을 말하는 것이 아닙니다. 사실, 나는 당신에 동의합니다. 나는 그것이 실제로 묻는 질문에 대답하지 않는다고 말하고 있습니다. " 게임에 오디오를 가져 오려면 어떻게해야합니까? "라고 물었고 " 내가 오디오를 가지고 있지 않다는 것을 믿을 수 없습니다. 지금 게임에 오디오를 넣으십시오. " 실제 답변 인 " (FMOD를 사용할 수 있습니다) "를 넣습니다 .
Tom Dalling

6
@TrevorPowell 여기가 잘못되었습니다. 대안을 모른다면 "징계"가 아닙니다. 내가 준 예제 코드는 순전히 이론적입니다. 나는 그것이 나쁜 디자인이라는 것을 이미 알고 있지만 C ++은 녹슨 것이므로 실제로 원하는 것을 코딩하기 전에 더 나은 디자인에 대해 물어볼 것이라고 생각했습니다. 그래서 대체 디자인에 대해 물어 보았습니다. " 삭제 플래그 추가 "는 대안입니다. " 하지 마십시오 "는 대안 이 아닙니다 . 내가 이미 알고있는 것을 말해줍니다. 질문을 제대로 읽지 않고 답을 쓴 것처럼 느껴집니다.
Tom Dalling

4
@Bobby 문제는 "X를하지 않는 방법"입니다. 단순히 "X를하지 마십시오"라고 말하는 것은 쓸모없는 대답입니다. 질문이 "X를 해봤 다"또는 "X를 할 생각했었다"또는 그 변형이 있다면 메타 토론의 매개 변수를 충족하지만 현재의 형식은 아닙니다.
Joshua Drake

21

버퍼에서 이벤트를 큐에 넣는 대신 버퍼에서 삭제 를 큐에 넣습니다 . 지연된 삭제는 논리를 크게 단순화 할 수있는 잠재력을 가지고 있습니다. 객체에 흥미로운 일이 없다는 것을 알면 실제로 프레임의 끝이나 시작 부분에서 메모리를 확보하고 절대적으로 어디에서나 삭제할 수 있습니다.


1
NSAutoreleasePool이 상황에서 Objective-C가 얼마나 좋을지 생각했기 때문에 이것을 언급하는 것이 재미 있습니다 . DeletionPoolC ++ 템플릿이나 무언가 로 만들어야 할 수도 있습니다 .
Tom Dalling

@TomDalling 객체 외부에서 버퍼를 만드는 경우주의해야 할 한 가지 사항은 단일 프레임에서 여러 이유로 객체를 삭제하고 여러 번 삭제하려고 시도 할 수 있다는 것입니다.
John Calsbeek

매우 사실입니다. 포인터를 std :: set에 유지해야합니다.
Tom Dalling

5
삭제할 객체 버퍼 대신 객체에서 플래그를 설정할 수도 있습니다. 런타임 중에 new를 호출하거나 삭제하지 않고 객체 풀로 이동하려는 양을 깨닫기 시작하면 더 간단하고 빠릅니다.
Sean Middleditch

4

세계가 삭제를 처리하도록하는 대신 다른 클래스의 인스턴스가 삭제 된 모든 엔터티를 저장하는 버킷 역할을하도록 할 수 있습니다. 이 특정 인스턴스는 ENTITY_DEATH이벤트를 수신하고 대기하도록 이벤트를 처리해야합니다. 는 World프레임이 렌더링 차례로 엔티티 인스턴스의 실제 삭제를 수행 할 '명확한'이 양동이, 한 후 다음으로 반복 이러한 경우 이상 및 사후 죽음의 작업을 수행 할 수 있습니다.

이러한 클래스의 예는 다음과 같습니다. http://ideone.com/7Upza


+1, 엔터티에 직접 플래그를 지정하는 대신 사용할 수 있습니다. 더 직접적으로, World클래스에 직접 살아있는 목록과 죽은 개체 목록을 갖습니다 .
Laurent Couvidou

2

게임의 모든 게임 오브젝트 할당에 사용되는 팩토리를 구현하는 것이 좋습니다. 따라서 새로운 전화를하는 대신 공장에서 무언가를 만들라고 지시 할 것입니다.

예를 들어

Object* o = gFactory->Create("Explosion");

객체를 삭제하려고 할 때마다 팩토리는 다음 프레임에서 삭제 된 버퍼에 객체를 푸시합니다. 대부분의 시나리오에서 지연 파괴는 매우 중요합니다.

또한 한 프레임 지연으로 모든 메시지를 보내십시오. 즉시 보내야 할 경우가 많지만 대부분의 경우 예외가 있습니다.


2

C ++에서 관리 메모리를 직접 구현할 수 있으므로 ENTITY_DEATH호출 될 때 발생하는 모든 참조 수가 1 씩 줄어 듭니다.

나중에 @John이 모든 프레임의 구걸에서 제안한 것처럼 쓸모없는 엔티티 (0 참조가있는 엔티티)를 확인하고 삭제할 수 있습니다. 예를 들어 boost::shared_ptr<T>( here 문서화 됨 ) 또는 C ++ 11 (VC2010)을 사용하는 경우std::tr1::shared_ptr<T>


단지 std::shared_ptr<T>기술 보고서가 아닙니다! — 사용자 정의 삭제기를 지정해야합니다. 그렇지 않으면 참조 카운트가 0에 도달하면 즉시 객체도 삭제됩니다.
leftaroundabout

1
@ leftaroundabout 적어도 실제로 gcc에서 tr1을 사용해야했습니다. 그러나 VC에서는 그럴 필요가 없었습니다.
Ali1S232

2

풀링을 사용하고 실제로 객체를 삭제하지 마십시오. 대신 등록 된 데이터 구조를 변경하십시오. 예를 들어 객체를 렌더링하는 경우 렌더링, 충돌 감지 등을 위해 장면 객체와 모든 엔티티가 어떻게 든 등록되어 있습니다. 객체를 삭제하는 대신 장면에서 분리하여 죽은 객체 풀에 삽입하십시오. 이 방법은 메모리 문제 (예 : 객체 자체 삭제)를 방지 할뿐만 아니라 풀을 올바르게 사용하면 게임 속도를 높일 수도 있습니다.


1

우리가 게임에서 한 일은 새로운 게재 위치를 사용하는 것이 었습니다

SomeEvent* obj = new(eventPool.alloc()) new SomeEvent();

eventPool은 큰 메모리 배열로 각 세그먼트에 대한 포인터가 저장되었습니다. 따라서 alloc ()은 사용 가능한 메모리 블록의 주소를 반환합니다. eventPool에서 메모리는 스택으로 취급되었으므로 모든 이벤트가 전송 된 후 스택 포인터를 배열의 시작 부분으로 다시 재설정했습니다.

이벤트 시스템의 작동 방식으로 인해 evetns에서 소멸자를 호출 할 필요가 없었습니다. 따라서 풀은 단순히 메모리 블록을 사용 가능한 것으로 등록하고 할당합니다.

이것은 우리에게 큰 속도를 주었다.

또한 ... 실제로 게임이 종료 될 때 풀에 남아있는 객체가있는 경우 메모리 누수를 찾는 좋은 방법 이었기 때문에 개발 중에 동적으로 할당 된 모든 객체에 대해 메모리 풀을 실제로 사용했습니다. 멤 누출.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.