2013-05-17 2 views
5

Я пытаюсь выполнить следующее:Производный класс без накладных расходов с использованием шаблонов?

  1. Объект.
  2. Отладка версии объекта с дополнительной функциональностью в функции для отслеживания.

Теперь у меня есть решение для компиляции с использованием макросов, которые разрешают do {} while(0), если библиотека не скомпилирована с соответствующим флагом.

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

Я бы хотел: Base * obj = (isGlobalDebugEnabled) ? new Debug(...) : new Base(...); тип предмет. Неужели я не хочу чего-то подобного?

Примечание. Стандартная виртуальная функция на самом деле не решает проблему, так как каждая функция должна дублироваться в производной (отладочной) версии объекта, превзойдя цель.

Кроме того, функции нижнего уровня чрезвычайно велики (более 600 миллиардов вызовов при профилировании), поэтому я хочу иметь скомпилированное нулевое служебное решение для «базового класса». Разумеется, объект Debug может быть медленнее.

Вот почему я подумал о шаблонах. ПРИМЕЧАНИЕ. У меня нет доступа к C++ 11/boost помимо функций VS2010 (базовые lambdas и т. Д.). Могу ли я сделать что-то вроде

template <bool debug = false> 
class Object { 
    std::enable_if<debug> void printTrace(); // Add functions based on debug/not 
}; 
void Object::doSomething(...){ 
    <only do this if debug without runtime check> addToTrace(...); 
    doTheStuff(); 
} 

Я видел this link который указал мне в направлении поддельного наследования с помощью шаблонов, если это помогает.

Спасибо за вашу помощь

AK

EDIT: Я просто понял, что может идти об этом неправильном пути - может быть, есть объект Debug в качестве базового класса, и переопределить функциональность no-ops в объекте Regular. Кажется, это лучший способ. Тем не менее, я все равно хотел бы избежать прыжка vtable из-за этих высокопроизводительных требований, поэтому, я думаю, мои вопросы по шаблону все еще стоят? может быть?

EDIT2: Как KerrickSB указывалось, примером использования может быть более ясным:

основной код EXE:

void ComputeSomething() { 
    Object * obj = (globalDebugFlag) ? new DebugObject(...) : new Object(...); 
    obj->insertElement(elem); // Inserts in Object, Inserts and traces actions in DebugObject 
    ... 
} 

, где объект находится в отдельной DLL, и где globalDebugFlag является (предлагаемая) глобальная переменная, заданная командой, поступающей через отдельный порт, чем та, которая вызывала вызов ComputeSomething().

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

+0

Являются ли функции отладки всегда одинаковыми, то есть есть общий «интерфейс отладки»? –

+0

Если функциональность отладки не переплетена, вы можете просто создать функции отладки в производном классе Debug следующим образом: 'virtual void foo() {/ * debug stuff */Base :: foo(); } ' В противном случае, я думаю, вы могли бы просто разделить функциональность на несколько функций (возможно, встроенный? Не уверен, можете ли вы это сделать) и использовать этот подход в каждом месте. – Svalorzen

+0

Не могли бы вы привести простой пример того, как вы хотели бы * использовать * свое решение (возможно, в псевдокоде)? –

ответ

3

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

Тем не менее, вы можете нажать проверку стека вызовов до тех пор, пока они не попадут нечасто достаточно для ваших нужд. Конечно, эффект изменения флага отладки немного задерживается (насколько зависит от того, какие проверки вы элита) , Используя шаблоны, вы можете дублировать/специализировать код для отладки и не отладки версий без дублирования исходного кода.

template <bool debug> 
class Object { 
    void something() { 
    // branch on compile-time constant - can be optimized 
    if (!debug) return; 
    // ... 
    } 
} 

template<bool debug> 
useObject(Object<debug> o) { 
    for(int i = 0; i < 10000; ++i) { 
     // statically calls specialized implementation 
     o.something(); 
    } 
} 

debugEnabled ? useObject(Object<true>()) : useObject(Object<false>()); 
+1

«Решение времени выполнения означает, по определению, что вы принимаете решение со всеми его расходами во время выполнения, в отличие от компиляции Ты не собираешься с этим справиться. Ну, это зависит от того, как это делается. Если это делается путем замены фабрики на нормальные объекты на фабрику, которая создает объекты отладки, тогда для нормального случая может быть нулевой накладной. – bames53

+2

@ bames53 Код, использующий объекты, также должен либо взаимодействовать через виртуальный, либо стираемый по типу интерфейс, либо он также должен дублироваться для двух случаев. – Yakk

+1

@ bames53 Моя реторта использует детали в зависимости от того, как вы реализуете этот шаг «сменить заводскую» и как вы * действуете * по-разному на этих разных объектах. Предполагая, например, что эта фабрика является глобальным указателем функций, а заводские функции возвращают указатели на полиморфные объекты, вы возвращаетесь к отправке vtable для операций над объектом. Кроме того, вызов на заводе косвенен, поэтому заводский вызов не может быть встроен или иным образом оптимизирован (без знания потенциального сглаживания). – delnan

1

Вот очень простая идея. Я не уверен, что он будет обобщать или масштабировать, но мы можем обсудить.

static bool debug_mode = /* ... */; // global 

class Container 
{ 
    struct ContainerImpl 
    { 
     virtual ~ContainerImpl() { } 
     virtual void insert(int) = 0; 
     std::unique_ptr<ContainerImpl> clone() const = 0; 
    }; 

    std::unique_ptr<ContainerImpl> impl; 
public: 
    Container() 
    : impl(debug_mode ? new DebugImpl : new MainImpl) 
    { } 

    Container(Container const & rhs) 
    : impl(rhs.impl->clone()) 
    { } 

    Container(Container && rhs) noexcept 
    : impl(std::move(rhs.impl)) 
    { } 

    // also implement assignment 


    /*** Main interface ***/ 

    void insert(int x) 
    { 
     impl->insert(x); 
    } 


    /*** Implementations ***/ 

    struct MainImpl : ContainerImpl { /* main implementation */ }; 

    struct DebugImpl : MainImpl // just for example 
    { 
     virtual void insert(int x) 
     { 
      // trace insertion 
      MainImpl::insert(x); 
     } 

     std::unique_ptr<ContainerImpl> clone() const 
     { 
      return { new DebugImpl(*this); } 
     } 
    }; 
}; 

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

+0

hmmmm чрезвычайно интересная идея - позвольте мне спать и вернемся к этому завтра, чтобы лучше обсудить это с вами. Спасибо за ваш ответ! Кроме того, я добавил дополнительный пояснительный комментарий в рамках ОП, возможно, немного осмыслив окружающую среду. EDIT - также, когда я сплю, вы можете объяснить, где именно вы переместили команду asm vtable jump asm? Если я - очень быстро - просматривая это правильно, это до точки построения объекта, за которой следуют обычные вызовы func в вызывающем коде? –

+0

@ AK4749: виртуальная отправка происходит в полиморфном частном объекте ContainerImpl. Этот код в основном представляет собой вариант стандартной идиомы pimpl, с простой условной для того, хотите ли вы отладочную версию. –

+0

Ах, теперь я понимаю, как это работает. Это аккуратный трюк - никогда раньше не изучали идиомы Opaque Pointer. Однако, по нескольким причинам, разбросанным по этому сообщению, это может быть недействительным решением в моем случае. Если я это правильно пойму, ptr2Container-> insert (x) все равно отправит, чтобы выяснить, какую реализацию использовать для каждого вызова. Это верно? Я понимаю значительно улучшенную ремонтопригодность при относительно низком воздействии на производительность, но, как правило, у клиентов нет - и я не имею достаточно высокого ранга, чтобы сражаться с этой битвой, если с ней справится. –

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