엔진 구성 요소의 생성자와 소멸자에 논리를 넣는 대신 별도의 초기화 및 정리 방법을 사용해야하는 이유는 무엇입니까?


9

저는 자체 게임 엔진을 개발 중이며 현재 관리자를 설계하고 있습니다. 메모리 관리의 경우 생성자와 소멸자를 사용하는 것보다 Init()CleanUp()기능이 더 좋습니다.

C ++ 코드 예제를 찾고 그 함수의 작동 방식과 엔진에 구현하는 방법을 확인했습니다. 어떻게 Init()CleanUp()작업 방법과 내 엔진으로이를 구현할 수있다?



C ++의 경우 stackoverflow.com/questions/3786853/…을 참조하십시오. Init ()를 사용하는 주요 이유는 1) 도우미 함수를 사용하여 생성자의 예외 및 충돌 방지 2) 파생 클래스의 가상 메서드를 사용할 수 있음 3) 순환 종속성 우회 4) 코드 중복을 피하기위한 개인적인 방법으로
brita_

답변:


12

실제로는 매우 간단합니다.

설정을 수행하는 생성자가 아닌,

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

... 생성자가 거의 또는 전혀 수행하지 않도록하고 생성자가 일반적으로하는 일을하는 .initor 메소드를 작성 .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주거나 (또는 ​​반대로 초기화를 더 분리하여 로딩을 분리 한다는 점을 제외하고) 일반적인 것의 차이점은 무엇입니까? 로드 된 것을 설정하고 모든 것이 설정되면 프로그램을 실행하십시오).


2
" 그런 다음 수동으로 호출하여 프로그램에서 발생한 시간 과 위치를 정확히 알 수 있습니다. "C ++에서 소멸자가 암시 적으로 호출되는 유일한 시간은 스택 객체 (또는 전역)입니다. 힙 할당 객체는 명시 적 파기가 필요합니다. 따라서 객체가 할당 해제되는 시점은 항상 명확합니다.
Nicol Bolas

6
다른 무기 유형을 주입 할 수있는 별도의 방법이 필요하거나 이것이 서브 클래스의 확산을 피할 수있는 유일한 방법이라고 말하는 것은 정확하지 않습니다. 생성자를 통해 무기 인스턴스를 전달할 수 있습니다! 그래서 이것은 강력한 유스 케이스가 아니기 때문에 나에게서 -1입니다.
Kylotan

1
-1 Kylotan과 거의 같은 이유로 나 에게서도. 당신은 매우 설득력있는 주장을하지 않습니다.이 모든 것은 생성자로 할 수 있습니다.
Paul Manta

예, 생성자와 소멸자로 수행 할 수 있습니다. 그는 기술의 유스 케이스와 작동 방식 또는 이유보다는 이유와 방법을 요구했습니다. DI에 대한 생성자 전달 매개 변수와 설정 기 / 바인딩 방법이있는 구성 요소 기반 시스템을 사용하면 실제로 인터페이스를 구축하려는 방법이 결정됩니다. 그러나 객체에 20 개의 IOC 구성 요소가 필요한 경우 모든 구성 요소를 생성자에 넣기를 원하십니까? 너는 할수 있니? 물론 당신은 할 수. 당신은해야합니까? 그럴 수도 있고 아닐 수도 있고. 당신이 선택하면 하지 에, 당신이 필요합니까 .init아마,하지만 가능성. Ergo, 유효한 사건.
Norguard

1
@Kylotan 실제로 질문 제목을 편집하여 이유를 묻습니다. OP는 "어떻게"만 물었다. 나는 "어떻게"가 프로그래밍에 대해 아는 사람에게는 사소한 것이기 때문에 "왜"를 포함하도록 질문을 확장했다 ( "만약 당신이 ctor에 가질 논리를 별도의 함수로 옮기고 호출") 그리고 "왜" 더 흥미롭고 일반적인 것입니다.
Tetrad

17

당신이 읽은 것이 무엇이든 Init와 CleanUp이 더 낫다는 것도 이유를 말해 주었을 것입니다. 그들의 주장을 정당화하지 않는 기사는 읽을 가치가 없습니다.

별도의 초기화 및 종료 기능을 사용하면 호출 순서를 선택할 수 있기 때문에 시스템을 쉽게 설정하고 파괴 할 수 있지만, 생성자는 객체가 생성 될 때 정확하게 호출되고 객체가 파괴 될 때 소멸자가 호출됩니다. 두 개체 사이에 복잡한 종속성이있는 경우, 개체를 설정하기 전에 둘 다 존재해야하는 경우가 종종 있습니다.

일부 언어에는 참조 카운트와 가비지 콜렉션으로 인해 오브젝트가 언제 파괴되는지 알기 어렵 기 때문에 신뢰할 수있는 소멸자가 없습니다. 이 언어들에서는 거의 항상 셧다운 / 정리 방법이 필요하며, 일부는 대칭을위한 init 방법을 추가하는 것을 좋아합니다.


고맙지 만 기사가 없었기 때문에 주로 예제를 찾고 있습니다. 내 질문에 대해 명확하지 않은 경우 사과하지만 지금 편집했습니다.
Friso

3

가장 좋은 이유는 풀링을 허용하는 것입니다.
Init 및 CleanUp이있는 경우 개체가 종료되면 CleanUp을 호출하고 같은 유형의 개체 스택에 '풀'을 밀어 넣습니다.
그런 다음 새 객체가 필요할 때마다 풀에서 하나의 객체를 팝업하거나 풀이 비어있는 경우 새 객체를 만들어야합니다. 그런 다음이 객체에서 Init를 호출합니다.
좋은 전략은 게임이 '좋은'개체 수로 시작하기 전에 풀을 미리 채우는 것이므로 게임 중에 풀링 된 개체를 만들 필요가 없습니다.
반면에 'new'를 사용하고 객체를 사용하지 않을 때 객체 참조를 중단하면 가비지가 생겨 언젠가는 수집해야합니다. 이 기억은 가비지 수집기가 더 이상 사용하지 않는 객체의 메모리를 수집해야한다고 평가할 때 가비지 수집기가 모든 코드를 중지하는 Javascript와 같은 단일 스레드 언어의 경우 특히 좋지 않습니다. 몇 밀리 초 동안 게임이 멈추고 연주 경험이 손상됩니다.
-당신은 이미 이해했습니다-: 당신이 모든 물건을 모으면, 추억이 일어나지 않으므로 더 이상 무작위로 느려지지 않습니다.

또한 메모리를 할당하고 새 객체를 초기화하는 것보다 풀에서 오는 객체에서 init를 호출하는 것이 훨씬 빠릅니다.
그러나 객체 생성이 성능 병목 현상이 아니기 때문에 속도 향상의 중요성은 덜 중요합니다 ... 열광적 인 게임, 파티클 엔진 또는 계산에 집중적으로 2D / 3d 벡터를 사용하는 물리 엔진과 같은 몇 가지 예외가 있습니다. 풀을 사용하면 속도와 가비지 생성이 크게 향상됩니다.

Rq : Init ()가 모든 것을 재설정하면 풀링 된 객체에 대해 CleanUp 메서드가 필요하지 않을 수 있습니다.

편집 :이 게시물에 회신하면 내가 자바 스크립트에서 풀링 대해 작성한 작은 기사를 마무리하도록 동기를 부여 했습니다 .
당신이 관심이 있다면 여기에서 찾을 수 있습니다 :
http://gamealchemist.wordpress.com/


1
-1 : 개체 풀을 갖기 위해이 작업을 수행 할 필요는 없습니다. 배치 새 배치를 통해 구성에서 할당을 분리하고 명시 적 소멸자 호출로 삭제에서 할당 취소를 수행하면됩니다. 따라서 이것은 생성자 / 소멸자를 일부 이니셜 라이저 메소드와 분리하는 유효한 이유가 아닙니다.
Nicol Bolas

새로운 배치는 C ++에 따라 다르며 약간 난해합니다.
Kylotan

+1 다른 방법으로 c +에서이 작업을 수행 할 수 있습니다. 그러나 다른 언어는 아니지만 ... 이것은 아마도 gameobjects에서 Init 메소드를 사용하는 이유 일 것입니다.
Kikaimaru

1
@Nicol Bolas : 당신이 과민 반응하고 있다고 생각합니다. 풀링을 수행하는 다른 방법이 있다는 사실 (C ++에 복잡한 것을 언급)은 별도의 Init을 사용하는 것이 많은 언어로 풀링을 구현하는 훌륭하고 간단한 방법이라는 사실을 무효화하지 않습니다. 내 환경 설정은 GameDev에서보다 일반적인 답변으로 이동합니다.
GameAlchemist

@VincentPiel : C ++에서 게재 위치를 새롭고 "복잡한"사용하는 방법은 무엇입니까? 또한 GC 언어로 작업하는 경우 객체에 GC 기반 객체가 포함될 가능성이 높습니다. 그래서 그들은 또한 그들 각각을 조사해야할까요? 따라서 새 객체를 만들려면 풀에서 많은 새 객체를 가져옵니다.
Nicol Bolas 2013

0

귀하의 질문은 반대입니다 ... 역사적으로 더 적절한 질문은 다음과 같습니다.

건설 + 초기화 혼란스러워 합니까? 즉, 이러한 단계를 별도로 수행 하지 않는 이유 무엇입니까? 확실히 이것은 SoC어긋나는가 ?

C ++의 경우 RAII 의 의도는 리소스 확보와 릴리스가 리소스 수명과 직접 연결되어 리소스 릴리스를 보장한다는 것입니다. 그렇습니까? 부분적으로 스택 기반 / 자동 변수와 관련하여 100 % 충족됩니다. 여기서 관련 범위를 떠나면 자동으로 소멸자를 호출 / 해제합니다 (따라서 한정자 automatic). 그러나 힙 변수의 경우이 매우 유용한 패턴은 슬프게 나뉩 delete니다. 소멸자를 실행하기 위해 명시 적으로 호출해야하며 , 잊어 버린 경우 RAII가 해결하려고 시도하는 것에 여전히 물릴 것입니다. 힙 할당 변수와 관련하여 C ++은 C (delete 대를free()) 구성을 초기화와 병합하면서 다음과 같은 측면에서 부정적인 영향을 미칩니다.

C에서 게임 / 시뮬레이션을위한 객체 시스템을 구축하는 것은 C ++ 및 그 이후의 고전적인 OO 언어의 가정에 대한 깊은 이해를 통해 RAII 및 기타 이러한 OO 중심 패턴의 한계에 대해 많은 조명을 제공 할 것을 강력히 권장합니다. (C ++은 C에 내장 된 OO 시스템으로 시작했습니다.)

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