2013-08-12 3 views
4

Предположим, мы имеем следующий код:Является ли такой снижающий сейф?

#include <memory> 
#include <vector> 

struct BaseComponent 
{ 
    template <typename T> 
    T * as() 
    { 
     return static_cast<T*>(this); 
    } 

    virtual ~BaseComponent() {} 
}; 

template <typename T> 
struct Component : public BaseComponent 
{ 
    virtual ~Component() {} 
}; 

struct PositionComponent : public Component<PositionComponent> 
{ 
    float x, y, z; 

    virtual ~PositionComponent() {} 
}; 

int main() 
{ 
    std::vector<std::unique_ptr<BaseComponent>> mComponents; 
    mComponents.emplace_back(new PositionComponent); 

    auto *pos = mComponents[0]->as<PositionComponent>(); 
    pos->x = 1337; 

    return 0; 
} 

В Т * в качестве метода(), должен ли я использовать static_cast или dynamic_cast? есть времена, когда конверсия не сработает? Нужно ли мне вместо этого использовать dynamic_cast?

auto *ptr = dynamic_cast<T*>(this); 

    if(ptr == nullptr) 
     throw std::runtime_error("D'oh!"); 

    return ptr; 
+6

Это обычно называется ** down ** cast, а не upcast. –

+0

Каково использование аргумента шаблона T класса Component? – Jiwan

+1

@ Reinhard: Я думаю, вы рисуете свои иерархии, отличные от меня (и большинство других людей), потому что для меня основы вверх, а производные типы вниз ... Обратите внимание, что * естественная * вещь на языке * будет понято *. Некоторые культуры указывают на будущее, некоторые указывают на прошлое, и в каждой культуре, указывающей в неправильном направлении, возникает путаница. Когда вы работаете с OO, консенсус в том, что вверх идет к базе, вниз к производным типам –

ответ

2

Код, который вы представляете, является правильным и хорошо сформированным, но приведение в целом небезопасно. Если фактический объект не был PositionComponent, то компилятор с радостью предположил бы, что это так, и вы будете вызывать неопределенное поведение.

Если вы замените литой dynamic_cast, тогда компилятор сгенерирует код, который во время выполнения проверяет правильность преобразования.

Реальный вопрос, зачем вам это нужно. Есть причины, но чаще всего использование бросков является показателем проблем с вашим дизайном. Пересмотрите ли вы можете сделать лучше (то есть перепроектировать код так, что вам не нужно идти явное преобразование типов)

+0

Ну, я изучаю, как реализовать Entity-Component-System, Entity должен содержать свои компоненты, поэтому это одно решение. Но это уродливо, я согласен –

+1

@Reinhard: Что общего у компонентов, есть ли у них общий интерфейс? Если это так, просто используйте его. Нет необходимости знать, что такое конкретный компонент, если вы можете сказать ему, что делать, и он это делает ... –

+0

@ DavidRodríguez-dribeas Компоненты ничего не делают; они используются для хранения данных для объектов. –

1

Поскольку вы используете unique_ptr<BaseComponent>, естественно, может быть раз, когда преобразование не: введение новых данных в векторе и потребления этих данных делаются в несвязанных местах, и таким образом, что компилятор не может обеспечить его соблюдение.

Вот пример недопустимого гипса:

struct AnotherComponent : public Component<AnotherComponent> 
{ 
    virtual ~AnotherComponent() {} 
}; 

std::vector<std::unique_ptr<BaseComponent>> mComponents; 
mComponents.emplace_back(new AnotherComponent); 
// !!! This code compiles, but it is fundamentally broken !!! 
auto *pos = mComponents[0]->as<PositionComponent>(); 
pos->x = 1337; 

В связи с этим, использованием dynamic_cast обеспечит лучшую защиту от неправильного использования функции as<T>. Обратите внимание, что неправильное использование не может быть преднамеренным: в любое время компилятор не может проверить тип для вас, и у вас есть потенциальное несоответствие типов, то следует предпочесть dynamic_cast<T>

Вот small demo, чтобы проиллюстрировать, как dynamic_cast бы предложить вам степень защиты.

1

Вы должны всегда использовать dynamic_cast при литье полиморфных объектов, которые производятся из базового слоя.

В случае, когда mComponents[0] не является PositionComponent (или его производным от этого класса), приведенный выше код не сработает. Поскольку вся цель иметь mComponents имеет указатель на BaseComponent, так что вы можете поместить в вектор другие объекты, кроме PositionComponent объектов, я бы сказал, что вам нужно заботиться об этом конкретном сценарии.

В общем, это «плохой запах», когда вы используете dynamic_cast (или вообще кастинг объектов, полученных из общего базового слоя). Обычно это означает, что объекты не должны храниться в общем контейнере, потому что они недостаточно тесно связаны.

+0

** «Вы всегда должны использовать dynamic_cast при создании объектов, полученных из базового слоя». ** Если вы не знаете * тип. Кроме того, dynamic_cast следует использовать только для полиморфных объектов. Компонент может легко удалить виртуальную функцию, и она будет вести себя одинаково (если вы не используете деструктор). И в сущностных системах обычно вам не нужно использовать деструктор для компонентов, поскольку они POD. –

3

В вашем случае нет никакого способа узнать, статически this ли правильный типа или нет. То, что вы можете это CRTP (Любопытно повторяющийся узор шаблона):

template <class T> 
struct BaseComponent 
{ 
    T* as() 
    { 
     return static_cast<T*>(this); 
    } 

    virtual ~BaseComponent() {} 
}; 

template <typename T> 
struct Component : public BaseComponent<T> 
{ 
    virtual ~Component() {} 
}; 

struct PositionComponent : public Component<PositionComponent> 
{ 
    float x, y, z; 

    virtual ~PositionComponent() {} 
}; 

Таким образом, вы можете сделать:

auto x = yourBaseComponent.as(); 

и имеете право ребенок типа статический.

+0

Теперь он не может хранить указатели BaseComponent. –

+0

Да, но он не описал, как использовать шаблоны в его случае. Отсюда мое «то, что вы МОЖЕТЕ». – Jiwan

Смежные вопросы