이중 디스패치에 의존하지 않는 구현을 스케치 할 것입니다. 대신 모든 기능이 등록 된 테이블을 사용합니다. 이 테이블은 객체의 동적 유형 (기본 클래스로 전달됨)을 사용하여 액세스됩니다.
먼저, 몇 가지 예시 모양이 있습니다. 그들의 유형은 enum class
안에 들어 있습니다. 모든 도형 클래스는 해당 열거 형 항목으로 MY_TYPE
을 정의합니다. 또한, 그들은 기본 클래스 '순수 가상 type
방법을 구현해야한다 :
enum class ObjectType
{
Circle,
Box,
_Count,
};
class PhysicsObject
{
public:
virtual ObjectType type() const = 0;
};
class Circle : public PhysicsObject
{
public:
static const ObjectType MY_TYPE = ObjectType::Circle;
ObjectType type() const override { return MY_TYPE; }
};
class Box : public PhysicsObject
{
public:
static const ObjectType MY_TYPE = ObjectType::Box;
ObjectType type() const override { return MY_TYPE; }
};
다음, 당신은 그들이 물론, 모양에 따라 구현 될 필요가, 당신의 충돌 해결 기능을 가지고 있습니다.
void ResolveCircleCircle(Circle* c1, Circle* c2)
{
std::cout << "Circle-Circle" << std::endl;
}
void ResolveCircleBox(Circle* c, Box* b)
{
std::cout << "Circle-Box" << std::endl;
}
void ResolveBoxBox(Box* b1, Box* b2)
{
std::cout << "Box-Box" << std::endl;
}
주, 우리는 Circle
가지고 - 나는 그들의 충돌이 같은 방법으로 감지 가정으로 Circle
, - 여기 Box
, 아니 Box
을.Box
에 대한 자세한 내용 - Circle
충돌 사례. 코어 부에 지금
함수 테이블 :
std::function<void(PhysicsObject*,PhysicsObject*)>
ResolveFunctionTable[(int)(ObjectType::_Count)][(int)(ObjectType::_Count)];
REGISTER_RESOLVE_FUNCTION(Circle, Circle, &ResolveCircleCircle);
REGISTER_RESOLVE_FUNCTION(Circle, Box, &ResolveCircleBox);
REGISTER_RESOLVE_FUNCTION(Box, Box, &ResolveBoxBox);
테이블 자체는 std::function
(S)의 2 차원 어레이이다. 이러한 함수는 파생 클래스가 아닌 PhysicsObject
에 대한 포인터를 허용한다는 점에 유의하십시오. 그런 다음 등록을 쉽게하기 위해 일부 매크로를 사용합니다. 물론 각각의 코드는 손으로 작성 될 수 있으며 매크로 사용은 일반적으로 나쁜 습관으로 간주된다는 사실을 알고 있습니다. 그러나, 내 의견으로는, 이러한 종류의 것들은 매크로가 좋은 것이고, 네임 스페이스를 혼란스럽게하지 않는 의미있는 이름을 사용하는 한 허용됩니다. 다시 Circle
- Box
만 등록되고 다른 방법은 등록되지 않습니다. 이제 환상 매크로
:
#define CONCAT2(x,y) x##y
#define CONCAT(x,y) CONCAT2(x,y)
#define REGISTER_RESOLVE_FUNCTION(o1,o2,fn) \
const bool CONCAT(__reg_, __LINE__) = []() { \
int o1type = static_cast<int>(o1::MY_TYPE); \
int o2type = static_cast<int>(o2::MY_TYPE); \
assert(o1type <= o2type); \
assert(!ResolveFunctionTable[o1type][o2type]); \
ResolveFunctionTable[o1type][o2type] = \
[](PhysicsObject* p1, PhysicsObject* p2) { \
(*fn)(static_cast<o1*>(p1), static_cast<o2*>(p2)); \
}; \
return true; \
}();
매크로 고유 한 이름 변수 (행 번호를 사용하여)을 정의하지만,이 변수는 단순히 초기화 람다 함수가 실행되도록 내부 코드를 생성하는 역할을한다. 전달 된 두 개의 인수 (이들은 Box
및 Circle
이라는 구체적인 클래스)의 유형 (ObjectType
열거 형)을 가져 와서 테이블을 인덱싱하는 데 사용됩니다. 전체 메커니즘은 유형 (열거 형에 정의 된대로)에 대해 전체 순서가 있다고 가정하고 Circle
- Box
충돌에 대한 함수가 실제로이 순서로 인수에 등록되어 있는지 확인합니다. assert
은 실수로 잘못하고 있는지 알려줍니다 (우연히 Box
- Circle
). 그런 다음이 특정 쌍의 유형에 대해 테이블 내부에 람다 함수가 등록됩니다. 함수 자체는 유형이 PhysicsObject*
인 두 개의 인수를 취해 등록 된 함수를 호출하기 전에이를 구체적인 유형으로 변환합니다.
다음으로 테이블이 어떻게 사용되는지 살펴볼 수 있습니다.
void ResolveCollision(PhysicsObject* p1, PhysicsObject* p2)
{
int p1type = static_cast<int>(p1->type());
int p2type = static_cast<int>(p2->type());
if(p1type > p2type) {
std::swap(p1type, p2type);
std::swap(p1, p2);
}
assert(ResolveFunctionTable[p1type][p2type]);
ResolveFunctionTable[p1type][p2type](p1, p2);
}
, 인수의 동적 형태를 취하고 ResolveFunctionTable
내부 이러한 각 유형의 등록 기능에 전달 : 두 PhysicsObject
(S)의 충돌을 확인하는 하나의 기능을 구현하기 위해 현재 쉽다. 명령이 순서에 맞지 않으면 인수가 스왑된다는 점에 유의하십시오. 따라서 ResolveCollision
을 Box
및 Circle
과 함께 호출하면 Circle
- Box
충돌로 등록 된 함수가 내부적으로 호출됩니다.
마지막으로, 나는 그것을 사용하는 방법의 예를 줄 것이다 :
int main(int argc, char* argv[])
{
Box box;
Circle circle;
ResolveCollision(&box, &box);
ResolveCollision(&box, &circle);
ResolveCollision(&circle, &box);
ResolveCollision(&circle, &circle);
}
쉬운, 그렇지? 위의 작업 구현에 대해서는 this을 참조하십시오.
이제이 접근법의 장점은 무엇입니까? 위의 코드는 기본적으로 임의의 수의 도형을 지원하는 데 필요한 모든 것입니다. Triangle
을 추가하려고한다고 가정 해 보겠습니다. 당신이 할 수있는 일은 다음과 같습니다
- 가
ObjectType
열거에 항목 Triangle
을 추가합니다.
ResolveTriangleXXX
기능을 구현해야하지만 모든 경우에이 기능을 사용해야합니다.
- 하면 매크로를 사용하여 테이블에 등록 : 그것 뿐이다
합니다. PhysicsObject
에 메소드를 추가 할 필요가 없으므로 모든 기존 유형에 메소드를 구현할 필요가 없습니다.
매크로를 사용하는 것과 같이 모든 접근법이 중앙 테이블 enum
이고 글로벌 테이블에 의존하는 것과 같은이 접근법의 몇 가지 '결함'에 대해 알고 있습니다. 후자의 경우 셰이프 클래스가 여러 공유 라이브러리에 내장되어 있으면 문제가 발생할 수 있습니다. 그러나이 접근 방식은 다른 접근법 (예 : 이중 발송)의 경우처럼 코드가 폭발적으로 증가하지 않으므로 매우 유용합니다 (매우 특수한 경우는 제외).
[double dispatch] (https://en.wikipedia.org/wiki/Double_dispatch)를 참조하십시오. – Jarod42
문제는 명백합니다. 과부하는 2 개의 PhysicsObject를 인수로 사용하므로 오류가 발생합니다. 오버로드를 제공하거나'dynamic_cast'를 사용하십시오. –
물론 두 개의 PhysicsObjects를 사용하는 과부하를 생성 할 수는 있지만 여전히 동일한 유형의 물리 개체가 어떤 종류인지 알지 못하기 때문에 동일한 문제가 발생합니다. 감사합니다 @ Jarod42, 지금 읽어 줄 것입니다. – Beetroot