저는 자체 게임 엔진을 개발 중이며 현재 관리자를 설계하고 있습니다. 메모리 관리의 경우 생성자와 소멸자를 사용하는 것보다 Init()
및 CleanUp()
기능이 더 좋습니다.
C ++ 코드 예제를 찾고 그 함수의 작동 방식과 엔진에 구현하는 방법을 확인했습니다. 어떻게 Init()
및 CleanUp()
작업 방법과 내 엔진으로이를 구현할 수있다?
저는 자체 게임 엔진을 개발 중이며 현재 관리자를 설계하고 있습니다. 메모리 관리의 경우 생성자와 소멸자를 사용하는 것보다 Init()
및 CleanUp()
기능이 더 좋습니다.
C ++ 코드 예제를 찾고 그 함수의 작동 방식과 엔진에 구현하는 방법을 확인했습니다. 어떻게 Init()
및 CleanUp()
작업 방법과 내 엔진으로이를 구현할 수있다?
답변:
실제로는 매우 간단합니다.
설정을 수행하는 생성자가 아닌,
// c-family pseudo-code
public class Thing {
public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}
... 생성자가 거의 또는 전혀 수행하지 않도록하고 생성자가 일반적으로하는 일을하는 .init
or 메소드를 작성 .initialize
하십시오.
public class Thing {
public Thing () {}
public void initialize (a, b, c, d) {
this.x = a; /*...*/
}
}
이제는 다음과 같이 대신하십시오.
Thing thing = new Thing(1, 2, 3, 4);
당신은 갈 수 있습니다:
Thing thing = new Thing();
thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);
시스템에서 종속성 주입 / 제어 역전을보다 쉽게 사용할 수 있다는 이점이 있습니다.
말하는 대신
public class Soldier {
private Weapon weapon;
public Soldier (name, x, y) {
this.weapon = new Weapon();
}
}
당신은 군인을 구축 할 수 있습니다, 그에게 당신이만큼 방법, 줄 손을 그에게 무기를 한 다음 생성자 함수의 나머지 모두를 호출합니다.
이제, 한 군인에게는 권총이 있고 다른 군인에게는 소총이 있고 다른 군인에게는 산탄 총이있는 적을 하위 분류하는 대신 유일한 차이점은 다음과 같습니다.
Soldier soldier1 = new Soldier(),
soldier2 = new Soldier(),
soldier3 = new Soldier();
soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());
soldier1.initialize("Bob", 32, 48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92, 30);
파괴도 마찬가지입니다. 특별한 요구가있는 경우 (이벤트 리스너 제거, 배열에서 인스턴스 제거 / 작업중인 구조 등) 수동으로 호출하여 프로그램에서 언제 어디서 발생했는지 정확히 알 수 있습니다.
편집하다
아래에서 Kryotan이 지적했듯이, 이것은 원래 게시물의 "How"에 대답 하지만 실제로 "Why"를 잘 수행하지는 않습니다.
위의 답변에서 볼 수 있듯이 다음과 같이 큰 차이가 없을 수 있습니다.
var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();
그리고 쓰기
var myObj = new Object(1,2);
더 큰 생성자 기능을 가지고 있습니다.
15 개 또는 20 개의 사전 조건이있는 객체에 대해 논쟁을해야하는데, 이는 생성자를 매우 다루기 매우 어렵게 만들며, 인터페이스로 해당 객체를 꺼내서 더 쉽게보고 기억할 수있게합니다. 인스턴스화의 작동 방식을 한 수준 더 높일 수 있습니다.
객체의 선택적 구성은 이에 대한 자연스러운 확장입니다. 선택적으로 객체를 실행하기 전에 인터페이스에서 값을 설정합니다.
JS는이 아이디어에 대한 몇 가지 훌륭한 지름길을 가지고 있는데, 이는 더 강력한 유형의 c와 같은 언어에서는 제대로 보이지 않습니다.
즉, 생성자에서 오랫동안 인수 목록을 다루는 경우 객체가 너무 크고 너무 많이 할 가능성이 있습니다. 다시 말하지만 이것은 개인적인 취향의 일이며 멀리서도 예외가 있지만 객체에 20 가지를 전달하는 경우 더 작은 객체를 만들어서 객체가 덜 할 수있는 방법을 찾을 수 있습니다 .
보다 적절한 이유와 광범위하게 적용 가능한 이유는 객체의 초기화가 현재 가지고 있지 않은 비동기 데이터에 의존하기 때문입니다.
객체가 필요하다는 것을 알고 있으므로 어쨌든 객체를 생성해야하지만 제대로 작동하려면 서버 또는 현재로드해야하는 다른 파일의 데이터가 필요합니다.
다시 말하지만, 필요한 데이터를 거대한 초기화로 전달하든 인터페이스를 구축하든 객체의 인터페이스와 시스템 설계에 중요 할 정도로 개념에있어 중요한 것은 아닙니다.
그러나 객체를 만드는 관점에서 다음과 같이 할 수 있습니다.
var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);
async_loader
파일 이름이나 리소스 이름 또는 기타 리소스를 전달받을 수 있습니다. 어쩌면 사운드 파일이나 이미지 데이터를로드하거나 저장된 문자 통계를로드 할 수 있습니다 ...
... 그런 다음 해당 데이터를 다시 피드에 넣습니다 obj_w_async_dependencies.init(result);
.
이러한 종류의 동적 기능은 웹 앱에서 자주 발견됩니다.
예를 들어 갤러리가로드 및 초기화 된 다음 사진을 스트리밍 할 때 사진을 표시 할 수 있습니다. 예를 들어 갤러리는 실제로 비동기 초기화가 아니라 더 자주 보이는 위치입니다. JavaScript 라이브러리에서.
한 모듈이 다른 모듈에 종속 될 수 있으므로 종속 모듈의로드가 완료 될 때까지 해당 모듈의 초기화가 지연 될 수 있습니다.
이에 대한 특정 게임 인스턴스의 경우 실제 Game
클래스를 고려하십시오 .
왜 .start
또는 .run
생성자에서 전화를 걸 수 없습니까?
리소스를로드해야합니다. 나머지는 거의 정의되어 있고 사용하기에 좋습니다. 그러나 데이터베이스 연결이나 텍스쳐 나 모델, 사운드 또는 레벨없이 게임을 실행하려고한다면 특히 흥미로운 게임 ...
... 따라서 Game
"앞으로 나아가는"방법에 더 흥미로운 이름을 .init
주거나 (또는 반대로 초기화를 더 분리하여 로딩을 분리 한다는 점을 제외하고) 일반적인 것의 차이점은 무엇입니까? 로드 된 것을 설정하고 모든 것이 설정되면 프로그램을 실행하십시오).
.init
아마,하지만 가능성. Ergo, 유효한 사건.
당신이 읽은 것이 무엇이든 Init와 CleanUp이 더 낫다는 것도 이유를 말해 주었을 것입니다. 그들의 주장을 정당화하지 않는 기사는 읽을 가치가 없습니다.
별도의 초기화 및 종료 기능을 사용하면 호출 순서를 선택할 수 있기 때문에 시스템을 쉽게 설정하고 파괴 할 수 있지만, 생성자는 객체가 생성 될 때 정확하게 호출되고 객체가 파괴 될 때 소멸자가 호출됩니다. 두 개체 사이에 복잡한 종속성이있는 경우, 개체를 설정하기 전에 둘 다 존재해야하는 경우가 종종 있습니다.
일부 언어에는 참조 카운트와 가비지 콜렉션으로 인해 오브젝트가 언제 파괴되는지 알기 어렵 기 때문에 신뢰할 수있는 소멸자가 없습니다. 이 언어들에서는 거의 항상 셧다운 / 정리 방법이 필요하며, 일부는 대칭을위한 init 방법을 추가하는 것을 좋아합니다.
가장 좋은 이유는 풀링을 허용하는 것입니다.
Init 및 CleanUp이있는 경우 개체가 종료되면 CleanUp을 호출하고 같은 유형의 개체 스택에 '풀'을 밀어 넣습니다.
그런 다음 새 객체가 필요할 때마다 풀에서 하나의 객체를 팝업하거나 풀이 비어있는 경우 새 객체를 만들어야합니다. 그런 다음이 객체에서 Init를 호출합니다.
좋은 전략은 게임이 '좋은'개체 수로 시작하기 전에 풀을 미리 채우는 것이므로 게임 중에 풀링 된 개체를 만들 필요가 없습니다.
반면에 'new'를 사용하고 객체를 사용하지 않을 때 객체 참조를 중단하면 가비지가 생겨 언젠가는 수집해야합니다. 이 기억은 가비지 수집기가 더 이상 사용하지 않는 객체의 메모리를 수집해야한다고 평가할 때 가비지 수집기가 모든 코드를 중지하는 Javascript와 같은 단일 스레드 언어의 경우 특히 좋지 않습니다. 몇 밀리 초 동안 게임이 멈추고 연주 경험이 손상됩니다.
-당신은 이미 이해했습니다-: 당신이 모든 물건을 모으면, 추억이 일어나지 않으므로 더 이상 무작위로 느려지지 않습니다.
또한 메모리를 할당하고 새 객체를 초기화하는 것보다 풀에서 오는 객체에서 init를 호출하는 것이 훨씬 빠릅니다.
그러나 객체 생성이 성능 병목 현상이 아니기 때문에 속도 향상의 중요성은 덜 중요합니다 ... 열광적 인 게임, 파티클 엔진 또는 계산에 집중적으로 2D / 3d 벡터를 사용하는 물리 엔진과 같은 몇 가지 예외가 있습니다. 풀을 사용하면 속도와 가비지 생성이 크게 향상됩니다.
Rq : Init ()가 모든 것을 재설정하면 풀링 된 객체에 대해 CleanUp 메서드가 필요하지 않을 수 있습니다.
편집 :이 게시물에 회신하면 내가 자바 스크립트에서 풀링 에 대해 작성한 작은 기사를 마무리하도록 동기를 부여 했습니다 .
당신이 관심이 있다면 여기에서 찾을 수 있습니다 :
http://gamealchemist.wordpress.com/
귀하의 질문은 반대입니다 ... 역사적으로 더 적절한 질문은 다음과 같습니다.
건설 + 초기화 가 왜 혼란스러워 합니까? 즉, 이러한 단계를 별도로 수행 하지 않는 이유 는 무엇입니까? 확실히 이것은 SoC 에 어긋나는가 ?
C ++의 경우 RAII 의 의도는 리소스 확보와 릴리스가 리소스 수명과 직접 연결되어 리소스 릴리스를 보장한다는 것입니다. 그렇습니까? 부분적으로 스택 기반 / 자동 변수와 관련하여 100 % 충족됩니다. 여기서 관련 범위를 떠나면 자동으로 소멸자를 호출 / 해제합니다 (따라서 한정자 automatic
). 그러나 힙 변수의 경우이 매우 유용한 패턴은 슬프게 나뉩 delete
니다. 소멸자를 실행하기 위해 명시 적으로 호출해야하며 , 잊어 버린 경우 RAII가 해결하려고 시도하는 것에 여전히 물릴 것입니다. 힙 할당 변수와 관련하여 C ++은 C (delete
대를free()
) 구성을 초기화와 병합하면서 다음과 같은 측면에서 부정적인 영향을 미칩니다.
C에서 게임 / 시뮬레이션을위한 객체 시스템을 구축하는 것은 C ++ 및 그 이후의 고전적인 OO 언어의 가정에 대한 깊은 이해를 통해 RAII 및 기타 이러한 OO 중심 패턴의 한계에 대해 많은 조명을 제공 할 것을 강력히 권장합니다. (C ++은 C에 내장 된 OO 시스템으로 시작했습니다.)