2015-12-18 1 views
0

Этот вопрос вдохновлен this question, который спрашивал о вызове того же метода на разных типах, когда эти типы известны во время компиляции.обрабатывают неполиморфные объекты полиморфным способом без накладных расходов на производительность

Это заставило меня задуматься. Предположим, что у меня были разнородные неполиморфные типы, но я хотел использовать их полиморфно. Кроме того, я хочу сделать это, никогда не ссылаясь на new и delete, поскольку они являются известными узкими местами производительности.

Как бы я это сделал?

Обратите внимание, что это Q & Вопрос в стиле. Я предоставил ответ, который я придумал. Это не для привлечения upvotes (хотя это всегда приятно), но для того, чтобы поделиться проницательностью, которую я приобрел, работая над этой проблемой.

Другие ответы, безусловно, приглашены. Чем больше знаний мы разделяем, тем лучше все мы становимся.

ответ

2

Этот ответ был частично вдохновлен прекрасной работой, проделанной Беманом Доусом в библиотеке boost :: system_error.

Я изучил идею статического полиморфизма, изучив его фантастическую работу, которая теперь стала частью стандарта C++ 11. Беман, если ты когда-нибудь прочтешь это, поклонись.

Другим источником вдохновения был отличный разговор под названием Inheritance is the base class of evil по-настоящему одаренного Шона Родитель. Я тщательно рекомендую каждому разработчику C++ следить за ним.

Так достаточно этого, вот (мое) решение:

проблема:

У меня есть несколько типов объектов пользовательского интерфейса, которые не являются полиморфными (по соображениям производительности). Однако иногда я хочу называть show() или hide() методами по группам этих объектов.

Кроме того, я хочу, чтобы ссылки или указатели на эти объекты были полиморфными.

Кроме не все объекты, даже поддерживают show() и hide() методы, но это не имеет значения.

Кроме того, эксплуатационные накладные расходы должны быть как можно ближе к нулю.

Большое спасибо @ Jarod42 за предложение менее сложного конструктора для showable.

моего Solutuion:

#include <iostream> 
#include <vector> 
#include <utility> 
#include <typeinfo> 
#include <type_traits> 

// define an object that is able to call show() on another object, or emit a warning if that 
// method does not exist 
class call_show { 

    // deduces the presence of the method on the target by declaring a function that either 
    // returns a std::true_type or a std::false_type. 
    // note: we never define the function. we just want to deduce the theoretical return type 

    template<class T> static auto test(T* p) -> decltype(p->show(), std::true_type()); 
    template<class T> static auto test(...) -> decltype(std::false_type()); 

    // define a constant based on the above test using SFNAE 
    template<class T> 
    static constexpr bool has_method = decltype(test<T>(nullptr))::value; 

public: 

    // define a function IF the method exists on UIObject 
    template<class UIObject> 
    auto operator()(UIObject* p) const 
    -> std::enable_if_t< has_method<UIObject>, void > 
    { 
     p->show(); 
    } 

    // define a function IF NOT the method exists on UIObject 
    // Note, we could put either runtime error handling (as below) or compile-time handling 
    // by putting a static_assert(false) in the body of this function 
    template<class UIObject> 
    auto operator()(UIObject* p) const 
    -> std::enable_if_t< not has_method<UIObject>, void > 
    { 
     std::cout << "warning: show is not defined for a " << typeid(UIObject).name() << std::endl; 
    } 
}; 

// ditto for the hide method 
struct call_hide 
{ 
    struct has_method_ { 
     template<class T> static auto test(T* p) -> decltype(p->hide(), std::true_type()); 
     template<class T> static auto test(...) -> decltype(std::false_type()); 
    }; 

    template<class T> 
    static constexpr bool has_method = decltype(has_method_::test<T>(nullptr))::value; 

    template<class UIObject> 
    auto operator()(UIObject* p) const 
    -> std::enable_if_t< has_method<UIObject>, void > 
    { 
     p->hide(); 
    } 

    template<class UIObject> 
    auto operator()(UIObject* p) const 
    -> std::enable_if_t< not has_method<UIObject>, void > 
    { 
     std::cout << "warning: hide is not defined for a " << typeid(UIObject).name() << std::endl; 
    } 
}; 

// define a class to hold non-owning REFERENCES to any object 
// if the object has an accessible show() method then this reference's show() method will cause 
// the object's show() method to be called. Otherwise, error handling will be invoked. 
// 
class showable 
{ 
    // define the POLYMORPHIC CONCEPT of a thing being showable. 
    // In this case, the concept requires that the thing has a show() and a hide() method 
    // note that there is no virtual destructor. It's not necessary because we will only ever 
    // create one model of this concept for each type, and it will be a static object 
    struct concept { 
     virtual void show(void*) const = 0; 
     virtual void hide(void*) const = 0; 
    }; 

    // define a MODEL of the CONCEPT for a given type of UIObject 
    template<class UIObject> 
    struct model final 
    : concept 
    { 
     // user-provided constructor is necessary because of static construction (below) 
     model() {}; 

     // implement the show method by indirection through a temporary call_show() object 
     void show(void* p) const override { 
      // the static_cast is provably safe 
      call_show()(static_cast<UIObject*>(p)); 
     } 

     // ditto for hide 
     void hide(void* p) const override { 
      call_hide()(static_cast<UIObject*>(p)); 
     } 
    }; 

    // create a reference to a static MODEL of the CONCEPT for a given type of UIObject 
    template<class UIObject> 
    static const concept* make_model() 
    { 
     static const model<UIObject> _; 
     return std::addressof(_); 
    } 

    // this reference needs to store 2 pointers: 

    // first a pointer to the referent object 
    void * _object_reference; 

    // and secondly a pointer to the MODEL appropriate for this kind of object 
    const concept* _call_concept; 

    // we use pointers because they allow objects of the showable class to be trivially copyable 
    // much like std::reference_wrapper<> 

public: 

    // PUBLIC INTERFACE 

    // special handling for const references because internally we're storing a void* and therefore 
    // have to cast away constness 
    template<class UIObject> 
    showable(const UIObject& object) 
    : _object_reference(const_cast<void*>(reinterpret_cast<const void *>(std::addressof(object)))) 
    , _call_concept(make_model<UIObject>()) 
    {} 

    template<class UIObject> 
    showable(UIObject& object) 
    : _object_reference(reinterpret_cast<void *>(std::addressof(object))) 
    , _call_concept(make_model<UIObject>()) 
    {} 

    // provide a show() method. 
    // note: it's const because we want to be able to call through a const reference 
    void show() const { 
     _call_concept->show(_object_reference); 
    } 

    // provide a hide() method. 
    // note: it's const because we want to be able to call through a const reference 
    void hide() const { 
     _call_concept->hide(_object_reference); 
    } 
}; 


// 
// TEST CODE 
// 

// a function to either call show() or hide() on a vector of `showable`s 
void show_or_hide(const std::vector<showable>& showables, bool show) 
{ 
    for (auto& s : showables) 
    { 
     if (show) { 
      s.show(); 
     } 
     else { 
      s.hide(); 
     } 
    } 
} 

// a function to transform any group of object references into a vector of `showable` concepts 
template<class...Objects> 
auto make_showable_vector(Objects&&...objects) 
{ 
    return std::vector<showable> { 
     showable(objects)... 
    }; 
} 


int main() 
{ 
    // declare some types that may or may not support show() and hide() 
    // and create some models of those types 

    struct Window{ 
     void show() { 
      std::cout << __func__ << " Window\n"; 
     } 
     void hide() { 
      std::cout << __func__ << " Window\n"; 
     } 

    } w1, w2, w3; 

    struct Widget{ 

     // note that Widget does not implement show() 

     void hide() { 
      std::cout << __func__ << " Widget\n"; 
     } 

    } w4, w5, w6; 

    struct Toolbar{ 
     void show() 
     { 
      std::cout << __func__ << " Toolbar\n"; 
     } 

     // note that Toolbar does not implement hide() 

    } t1, t2, t3; 

    struct Nothing { 
     // Nothing objects don't implement any of the functions in which we're interested 
    } n1, n2, n3; 

    // create some polymorphic references to some of the models 
    auto v1 = make_showable_vector(w3, w4, n1, w5, t1); 
    auto v2 = make_showable_vector(n3, w1, w2, t2, w6); 

    // perform some polymorphic actions on the non-polymorphic types 
    std::cout << "showing my UI objects\n"; 
    show_or_hide(v1, true); 
    show_or_hide(v2, true); 

    std::cout << "\nhiding my UI objects\n"; 
    show_or_hide(v2, false); 
    show_or_hide(v1, false); 

    return 0; 
} 

пример вывод:

showing my UI objects 
show Window 
warning: show is not defined for a Z4mainE6Widget 
warning: show is not defined for a Z4mainE7Nothing 
warning: show is not defined for a Z4mainE6Widget 
show Toolbar 
warning: show is not defined for a Z4mainE7Nothing 
show Window 
show Window 
show Toolbar 
warning: show is not defined for a Z4mainE6Widget 

hiding my UI objects 
warning: hide is not defined for a Z4mainE7Nothing 
hide Window 
hide Window 
warning: hide is not defined for a Z4mainE7Toolbar 
hide Widget 
hide Window 
hide Widget 
warning: hide is not defined for a Z4mainE7Nothing 
hide Widget 
warning: hide is not defined for a Z4mainE7Toolbar 
+0

Любой причина, чтобы иметь '' вектор вместо того, 'tuple' и перебрать кортеж? – Jarod42

+0

Кстати, почему бы не использовать 2 перегрузки 'showable (const UIObject & object)'/'showable (UIObject & object)' вместо SFINAE на 'is_const'. – Jarod42

+0

@ Jarod42 есть. он должен продемонстрировать, что ссылка «showable» является полиморфной, поэтому функции могут быть записаны в терминах концепции «showable», например, теперь вы можете передать произвольную коллекцию showables для функции.Эти ярлыки будут ссылаться на неполитические объекты с стиранием. –

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