2011-01-26 2 views
7

Итак, предположим, что я хочу сделать серию классов, каждая из которых имеет функцию-член с той же самой вещью. Давайте называть функциюИзбегание виртуальных функций

void doYourJob(); 

Я хочу, чтобы в конечном итоге положить все эти классы в том же контейнере, так что я могу петлю через них и имеют каждый выполнять «doYourJob()»

очевидное решение, чтобы сделать абстрактный класс с функцией

virtual void doYourJob(); 

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

Есть ли способ избежать использования абстрактного класса с помощью виртуальной функции или мне придется его всасывать?

+1

Внедрите его, используя полиморфные классы. Весьма вероятно, что накладные расходы на отправку вызовов на виртуальные функции будут незначительными. В худшем случае это важно, и у вас будет хотя бы чистый дизайн, который можно легко оптимизировать. –

ответ

6

Виртуальные функции не стоят дорого. Это косвенный вызов, в основном как указатель на функцию. What is the performance cost of having a virtual method in a C++ class?

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

4

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

В этом контексте не так много для отправки виртуальной функции: поиск в vtable, настройка указателя this и косвенный вызов.

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

+3

'dynamic_cast' работает только для типов, имеющих по крайней мере одну виртуальную функцию, и если это так, вам гораздо лучше использовать эту виртуальную функцию для' doYourJob() '. – templatetypedef

+0

было бы так же просто, как использовать класс [code] class_class {virtual void doYourJob() {}} [code] или есть способ сделать его более эффективным – hedgehogrider

+1

Это действительно так просто. Вместо этого вы можете использовать 'virtual void doYourJob() = 0;', поэтому подклассы ** должны ** предоставлять реализацию вместо того, чтобы получать стандартную реализацию no-op. В любом случае, стоимость заключается в том, что вы добавляете один указатель на 'sizeof' каждого класса и должны сделать одну или две индикации во время разговора. Это несколько наносекунд. –

1

Если вы собираетесь хранить все эти объекты в одном контейнере, то либо вам придется писать гетерогенный тип контейнера (медленный и дорогой), вам нужно будет хранить контейнер void * s (yuck!), или классы должны быть связаны друг с другом через наследование. Если вы решите перейти к одному из первых двух вариантов, вам нужно будет иметь какую-то логику, чтобы посмотреть на каждый элемент в контейнере, выяснить, какой тип он есть, а затем вызвать соответствующую реализацию doYourJob(), которая по существу кипит вплоть до наследования.

Я настоятельно рекомендую попробовать простой, простой подход к использованию наследования в первую очередь. Если это достаточно быстро, это здорово! Все готово. Если это не так, попробуйте использовать другую схему. Никогда не избегайте полезной языковой функции из-за стоимости, если у вас нет веских доказательств того, что стоимость слишком велика.

8

Если вам нужна скорость, рассмотрите возможность вложения «типа (идентификационного номера)» в объекты и с помощью оператора switch, чтобы выбрать код типа. Это может полностью избежать накладных расходов функций - просто сделать локальный скачок. Вы не получите быстрее, чем это. Стоимость (с точки зрения ремонтопригодности, зависимостей перекомпиляции и т. Д.) Заключается в принуждении к локализации (в коммутаторе) специфических для конкретного типа функций.


РЕАЛИЗАЦИЯ

#include <iostream> 
#include <vector> 

// virtual dispatch model... 

struct Base 
{ 
    virtual int f() const { return 1; } 
}; 

struct Derived : Base 
{ 
    virtual int f() const { return 2; } 
}; 

// alternative: member variable encodes runtime type... 

struct Type 
{ 
    Type(int type) : type_(type) { } 
    int type_; 
}; 

struct A : Type 
{ 
    A() : Type(1) { } 
    int f() const { return 1; } 
}; 

struct B : Type 
{ 
    B() : Type(2) { } 
    int f() const { return 2; } 
}; 

struct Timer 
{ 
    Timer() { clock_gettime(CLOCK_MONOTONIC, &from); } 
    struct timespec from; 
    double elapsed() const 
    { 
     struct timespec to; 
     clock_gettime(CLOCK_MONOTONIC, &to); 
     return to.tv_sec - from.tv_sec + 1E-9 * (to.tv_nsec - from.tv_nsec); 
    } 
}; 

int main(int argc) 
{ 
    for (int j = 0; j < 3; ++j) 
    { 
    typedef std::vector<Base*> V; 
    V v; 

    for (int i = 0; i < 1000; ++i) 
     v.push_back(i % 2 ? new Base : (Base*)new Derived); 

    int total = 0; 

    Timer tv; 

    for (int i = 0; i < 100000; ++i) 
     for (V::const_iterator i = v.begin(); i != v.end(); ++i) 
      total += (*i)->f(); 

    double tve = tv.elapsed(); 

    std::cout << "virtual dispatch: " << total << ' ' << tve << '\n'; 

    // ---------------------------- 

    typedef std::vector<Type*> W; 
    W w; 

    for (int i = 0; i < 1000; ++i) 
     w.push_back(i % 2 ? (Type*)new A : (Type*)new B); 

    total = 0; 

    Timer tw; 

    for (int i = 0; i < 100000; ++i) 
     for (W::const_iterator i = w.begin(); i != w.end(); ++i) 
     { 
      if ((*i)->type_ == 1) 
       total += ((A*)(*i))->f(); 
      else 
       total += ((B*)(*i))->f(); 
     } 

    double twe = tw.elapsed(); 

    std::cout << "switched: " << total << ' ' << twe << '\n'; 

    // ---------------------------- 

    total = 0; 

    Timer tw2; 

    for (int i = 0; i < 100000; ++i) 
     for (W::const_iterator i = w.begin(); i != w.end(); ++i) 
      total += (*i)->type_; 

    double tw2e = tw2.elapsed(); 

    std::cout << "overheads: " << total << ' ' << tw2e << '\n'; 
    } 
} 

РЕЗУЛЬТАТЫ

В моей системе Linux:

~/dev g++ -O2 -o vdt vdt.cc -lrt 
~/dev ./vdt      
virtual dispatch: 150000000 1.28025 
switched: 150000000 0.344314 
overhead: 150000000 0.229018 
virtual dispatch: 150000000 1.285 
switched: 150000000 0.345367 
overhead: 150000000 0.231051 
virtual dispatch: 150000000 1.28969 
switched: 150000000 0.345876 
overhead: 150000000 0.230726 

Это предполагает, что приблизительный подход с номером с номерами (1,28-0,23)/(0,344-0,23) = 9,2 раз быстрее. Конечно, это специфично для точных тестов системы/компилятора & и т. Д., Но в целом показатель.


КОММЕНТАРИИ RE виртуальной диспетчеризация

Надо сказать, однако, что виртуальная функция накладных расходы вызова является то, что это редко значительным, а затем только для часто называемых тривиальных функций (например, получение и установка). Даже тогда вы могли бы обеспечить единую функцию, чтобы получить и установить сразу множество вещей, минимизируя стоимость. Люди слишком беспокоятся о виртуальном пути отправки - так что делайте профилирование перед поиском неудобных альтернатив. Основная проблема заключается в том, что они выполняют вызов вне линии, хотя они также совершают выполненный код, который изменяет шаблоны использования кеша (лучше или (чаще) хуже).

+0

Почему код намного быстрее с '-lrt' с clang ++ и без? http://coliru.stacked-crooked.com/a/83ac0dc7d15b4747 – Gabriel

+0

Что такое флаг '-lrt', ссылка на библиотеку« rt »?? – Gabriel

+0

@Gabriel: yes - он свяжет librt.so - «rt» означает в реальном времени и содержит функцию 'clock_gettime'. –

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