2009-09-05 5 views
5

Я увидел одну книгу на C++, в которой упоминается, что перемещение иерархии наследования с использованием статического литья более эффективно, чем использование динамического приведения.Статический литье против dymamic cast для пересечения иерархий наследования

Пример:

#include <iostream> 
#include <typeinfo> 

using namespace std; 

class Shape { public: virtual ~Shape() {}; }; 
class Circle : public Shape {}; 
class Square : public Shape {}; 
class Other {}; 

int main() { 
    Circle c; 

    Shape* s = &c; // Upcast: normal and OK 

    // More explicit but unnecessary: 
    s = static_cast<Shape*>(&c); 
    // (Since upcasting is such a safe and common 
    // operation, the cast becomes cluttering) 

    Circle* cp = 0; 
    Square* sp = 0; 

    // Static Navigation of class hierarchies 
    // requires extra type information: 
    if(typeid(s) == typeid(cp)) // C++ RTTI 
     cp = static_cast<Circle*>(s); 
    if(typeid(s) == typeid(sp)) 
     sp = static_cast<Square*>(s); 
    if(cp != 0) 
     cout << "It's a circle!" << endl; 
    if(sp != 0) 
     cout << "It's a square!" << endl; 

    // Static navigation is ONLY an efficiency hack; 
    // dynamic_cast is always safer. However: 
    // Other* op = static_cast<Other*>(s); 
    // Conveniently gives an error message, while 
    Other* op2 = (Other*)s; 
    // does not 
} ///:~ 

Однако, как динамический и статический литой литой (как это реализовано выше) должен быть включен RTTI для навигации, например, чтобы работать. Просто динамический бросок требует, чтобы иерархия классов была полиморфной (т. Е. Базовый класс имел хотя бы одну виртуальную функцию).
Откуда этот коэффициент полезного действия для статического литья? В книге упоминается, что динамическое приведение является предпочтительным способом для тибетского понижения.

+3

Ой мои глаза. Отступ? –

+0

Передал бы ваше сообщение автору книги. – Ankur

+0

C-Style cast '(Other *) s' по существу такой же, как' reinterpret_cast (s) 'в этом случае, поэтому он компилируется без предупреждения (вы, по сути, говорите компилятору, чтобы он не беспокоился, вы знаете, что вы) – Attila

ответ

9

static_castпо себе НЕ нужно RTTI - typeid делает (как это делает dynamic_cast), но это совершенно другой вопрос. Большинство приведений просто говорят компилятору «доверься мне, я знаю, что я делаю» - dynamic_cast - это исключение, он просит компилятор проверить во время выполнения и, возможно, сбой. Это отличная разница в производительности!

+0

Вы видите какую-либо разницу в производительности с dynamic_cast и static_cast, как реализовано выше (в ней используется typeid). – Ankur

+4

@ankur, использование 'typeid' в приведенном выше коде очень специфично - оно проверяет ровно 2 типа. Если бы было какое-то дополнительное подклассов, проверка типа была бы неудачной. 'dynamic_cast' является намного более общим (особенно, поскольку ему также нужно иметь дело с МНОЖЕСТВЕННЫМ наследованием, независимо от того, используете ли вы его где-нибудь или нет!), и он должен работать нормально, даже если произошло дальнейшее подклассу, поэтому неудивительно, что это может быть помедленнее! –

+0

Получил ваш момент. Благодарю. – Ankur

1

dynamic_cast вернет NULL, если вы еще не выполнили проверку типа и не смогли добиться успеха. static_cast будет успешным (и приведет к неопределенному поведению, например, к возможному сбою). Скорее всего, разница в скорости.

+3

Исключение bad_cast будет выбрано, если мы будем использовать ссылки с dynamic_cast. С переменной указателя NULL будет возвращен за неправильную попытку. – Ankur

+0

Прохладный, спасибо за разъяснение. Я обновлю, чтобы отразить это. –

7

Это много лучше избегать включения типов вообще, если это возможно. Это обычно делается путем перемещения соответствующего кода в виртуальный метод, который реализуется по-разному для разных подтипов:

class Shape { 
public: 
    virtual ~Shape() {}; 
    virtual void announce() = 0; // And likewise redeclare in Circle and Square. 
}; 

void Circle::announce() { 
    cout << "It's a circle!" << endl; 
} 

void Square::announce() { 
    cout << "It's a square!" << endl; 
} 

// Later... 
s->announce(); 

Если вы работаете с уже существующей иерархией наследования, что вы не можете изменить, исследовать Visitor pattern для более расширяемая альтернатива переключению типов.

Подробнее:static_cast делает не требует RTTI, но подавленным, используя это может быть небезопасно, что приводит к неопределенному поведению (например, сбой). dynamic_cast безопасен, но медленный, потому что он проверяет (и поэтому требует) информацию RTTI. Старый стиль C-стиля еще более опасен, чем static_cast, потому что он будет спокойно использовать совершенно несвязанные типы, где static_cast будет возражать с ошибкой времени компиляции.

+0

Хотя это хороший совет в целом, это не тот ответ, который ищет Анкур, так как он не объясняет разницу между двумя приведениями. –

+1

Я полагаю, что вы правы - он в основном спросил: «Какой самый эффективный способ указать пистолет у моих ног?», И я глупо ответил: «Не прицеливайте пистолет у ваших ног», что не имеет значения , :-P –

+0

Да, тем не менее хороший наконечник. – Ankur

5

С помощью статического литья (и проверки типа) вы не можете downcast промежуточному типу (ребенок происходит от отца, который происходит от дедушки, вы не можете опуститься от дедушки к отцу), использование немного ограничено. static_cast без проверки TypeID жертвует правильность для Perfomance, и тогда вы не знаете, что они говорят:

Тот, кто жертвует правильность для исполнения заслуживает ни

Тогда, конечно, бывают ситуации, когда вы находитесь в отчаянной потребности из нескольких инструкций CPU, и больше не искать улучшения, и вы действительно безопасны в отношении того, что делаете, и у вас есть вопрос (правда?), что единственное место для получения производительности - использование static_cast вместо dynamic_cast ... тогда вы знайте, что вы должны переделать свой дизайн, или ваши алгоритмы, или получить лучшее оборудование.

Ограничения, которые вы налагаете с помощью rtti + static_cast, это то, что вы не сможете продлить свой код новыми производными классами позднее, не перерабатывая все места, где вы использовали этот трюк, чтобы получить только несколько инструкций процессора. Эта переработка, вероятно, займет больше времени (время разработки более дорогое), чем процессорное время, которое вы получили. Если, во всяком случае, время, затрачиваемое на downcasts, заметно, а затем переработайте свой дизайн, как предполагает j_random_hacker, он улучшится как в дизайне, так и в производительности.