2016-10-21 5 views
4

У меня есть метод в базовом классе, которому нужен тип, переданный ему для некоторых операций, связанных с типом (поиск, размер и вызов метода). В настоящее время это выглядит следующим образом:Вывести 'this' тип указателя при вызове из производного класса?

class base 
{ 
    template<typename T> 
    void BindType(T * t); // do something with the type 
}; 

class derived : public base 
{ 
    void foo() { do_some_work BindType(this); } 
}; 

class derivedOther : public base 
{ 
    void bar() { do_different_work... BindType(this); } 
}; 

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

class derived : public base 
{ 
    void foo() { BindType(); } 
}; 

без явного этого указателя , Я знаю, что я могу предоставить параметры шаблона явно как BindType<derived>(), но есть ли способ каким-то образом извлечь тип вызывающего?

+0

Это типичный прецедент для CRTP http://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp – Vinzenz

+0

использовать 'typeid (* this)' в базовый класс? – davmac

+0

@davmac typeid не дает мне тип, который я могу использовать для ..., например, call sizeof (T) или T :: SomeFunction() - это только дает мне typeid. – Steven

ответ

2

Он не будет работать, как вы ожидаете

Результат foo() может отличаться от того, что вы ожидаете:

class derived : public base   // <= YOU ARE IN CLASS DERIVED 
{ 
public: 
    void foo() { BindType(this); } // <= SO this IS OF TYPE POINTER TO DERIVED 
}; 

Шаблон параметра Я списывается на время компиляции, так что будет derived*. Если вы назовете foo() из класса derived_once_more, полученного от derived, он по-прежнему будет использовать тип derived*.

Online demo

Но вы можете избавиться от фиктивного параметра *

Вы можете использовать decltype(this) представлять TypeName переменной.Он по-прежнему определяется во время компиляции:

class base 
{ 
public: 
    template<typename T> 
    void BindType() 
    { 
     cout << typeid(T*).name()<<endl; // just to show the type 
    } 
    virtual ~base() {};     // for typeid() to work 
}; 
class derived : public base 
{ 
public: 
    void foo() { BindType<decltype(this)>(); } 
}; 

Online demo

Edit: другие альтернативы

Как нужно параметры шаблона должны быть предоставлены во время компиляции, а не время выполнения, вы можете использовать: параметр вычет

  • шаблон (ваш первый фрагмент кода)
  • decltype (see above)
  • , если вы собираетесь добавить это во всех производных классов, которые вы можете использовать макрос, чтобы сделать это, используя один из указанных выше упоминалось решение
  • вы можете использовать шаблон CRTP (уже объяснено в другом ответе).
+0

Да - Я согласен с тем, что тип этого специфичен для вызывающего контекста, даже если он вызван из производного класса. Я думаю, что все в порядке, поскольку меня больше всего беспокоит наличие большего контекста типа, чем базы. Это справедливая критика. параметр decltype (this), безусловно, удаляет параметр фиктивного типа, но требует более типизации в точке вызова. – Steven

+1

Насколько интересны все примеры и решения, я собираюсь пойти с моим оригинальным решением! BindToType (это) легко читается и не требует, чтобы люди, которые являются новыми для современного C++, понимают, что для нас делает decltype. Решение Christophe охватывает некоторые проблемы и решения, поэтому я считаю, что это лучший выбор для хорошего ответа. Спасибо всем. – Steven

3

Если вы хотите избежать BindType<derived>(), рассмотрите (немного подробней, я согласен) BindType<std::remove_reference<decltype(*this)>::type>();, чтобы избежать прохождения параметра. Он разрешается во время компиляции и позволяет избежать штрафов во время исполнения.

class base 
{ 
protected: 
    template<typename T> 
    void BindType() { cout << typeid(T).name() << endl; } // do something with the type 
}; 

class derived : public base 
{ 
public: 
    void foo() 
    { 
     BindType<std::remove_reference<decltype(*this)>::type>(); 
    } 
}; 
+2

Я не вижу в этом преимущества, явно указывая «производные» в угловых скобках (как указано в OP), если вы не планируете использовать макрос, не копируете и не вставляете. –

+0

@NirFriedman Я думаю, согласен - это более многословие. Я подозреваю, что большинство компиляторов «подойдут» и оптимизируют его. – Steven

+0

@Steven Ну, моя забота не связана с производительностью кода.По всей вероятности, код OP также, скорее всего, будет полностью оптимизирован (функция, называемая однажды, как правило, становится встроенной, и как только функция встроена, она переходит в игру). Я действительно думаю только о ясности кода. –

4

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

class base 
{ 
    template<typename T> 
    void BindType(); // do something with the type 
}; 

template <class T> 
class crtper : base 
{ 
    void BindDerived { BindType<T>(); } 
} 

class derived : public crtper<derived> 
{ 
    void foo() { do_some_work BindDerived(); } 
}; 

class derivedOther : public crtper<derivedOther> 
{ 
    void bar() { do_different_work... BindDerived(); } 
}; 

Редактировать: Следует упомянуть, я бы предположил, что foo будет виртуальной функцией, определенной без реализации в base. Таким образом, вы сможете запускать действие непосредственно из интерфейса. Хотя, возможно, у вас это есть в вашем реальном коде, но не в вашем примере. В любом случае это решение полностью совместимо с этим.

Редактировать 2: После редактирования вопроса отредактировано, чтобы уточнить, что решение по-прежнему применяется.

+0

Я не думаю, что мой образец был ясен - foo - простой метод, определенный в производном классе, специфичном для производного класса; использование BindType там не согласовано во всех производных классах базы. – Steven

+0

@Steven Мое решение все еще работает, просто нужно изменить имена вещей. См. Обновленный код. –

2

Возможное решения, которое позволяет избежать промежуточного класса CRTP следующим образом:

class base { 
    using func_t = void(*)(void *); 

    template<typename T> 
    static void proto(void *ptr) { 
     T *t = static_cast<T*>(ptr); 
     (void)t; 
     // do whatever you want... 
    } 

protected: 
    inline void bindType() { 
     func(this); 
    } 

public: 
    template<typename T> 
    base(T *): func{&proto<T>} {} 

private: 
    func_t func; 
}; 

struct derived1: base { 
    derived1(): base{this} {} 

    void foo() { 
     // ... 
     bindType(); 
    } 
}; 

struct derived2: base { 
    derived2(): base{this} {} 

    void bar() { 
     // ... 
     bindType(); 
    } 
}; 

int main() { 
    derived1 d1; 
    d1.foo(); 

    derived2 d2; 
    d2.bar(); 
} 

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

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


Вы можете добавить static_assert добавить ограничение на T, как пример:

template<typename T> 
base(T *t): func{&proto<T>} { 
    static_assert(std::is_base_of<base, T>::value, "!"); 
} 

Он требует, чтобы включить заголовок <type_traits>.

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