【问题标题】:treat non-polymorphic objects in a polymorphic way with no performance overhead以多态方式处理非多态对象,没有性能开销
【发布时间】:2016-03-25 10:56:47
【问题描述】:

这个问题受到this question 的启发,this question 询问在编译时已知不同类型的情况下对不同类型调用相同的方法。

这让我开始思考。假设我有不同的非多态类型,但我想多态地使用它们。此外,我想在不调用 newdelete 的情况下做到这一点,因为它们是已知的性能瓶颈。

我该怎么做?

请注意,这是一个问答式问题。我已经提供了我想出的答案。这不是为了吸引投票(尽管这总是很好),而是为了分享我在解决这个问题时获得的见解。

当然会邀请其他答案。我们分享的知识越多,我们就会变得越好。

【问题讨论】:

    标签: c++ performance polymorphism


    【解决方案1】:

    这个答案的部分灵感来自 Beman Dawes 在 boost::system_error 库上所做的出色工作。

    我通过研究他出色的工作了解到静态多态性的概念,该工作现已成为 c++11 标准的一部分。 Beman,如果你读过这篇文章,请鞠躬。

    另一个灵感来源是真正有天赋的 Sean Parent 的精彩演讲 Inheritance is the base class of evil。我强烈推荐每一位 C++ 开发者观看。

    够了,这是(我的)解决方案:

    问题:

    我有许多不是多态的 UI 对象类型(出于性能原因)。但是,有时我希望在这些对象的组上调用 show()hide() 方法。

    此外,我希望这些对象的引用或指针是多态的。

    此外并非所有对象都支持show()hide() 方法,但这不重要。

    此外运行时性能开销应尽可能接近于零。

    非常感谢@Jarod42 为showable 提出了一个不太复杂的构造函数。

    我的解决方案:

    #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
    

    【讨论】:

    • 有任何理由使用vector&lt;showable&gt; 而不是tuple 并遍历元组吗?
    • 顺便说一句,为什么不在 is_const 上使用 2 个重载 showable(const UIObject&amp; object)/showable(UIObject&amp; object) 而不是 SFINAE。
    • @Jarod42 是的。这是为了证明showable 引用是多态的,因此可以根据showable 概念编写函数,例如,您现在可以将任意运行时可显示的集合传递给函数。那些可展示的将是指类型擦除的非多边形对象。
    • 您的void* 是不需要的,您可以将成员直接放入模型中(您必须删除静态单例)。如果static 是为了避免分配,你仍然可以做一个新的展示位置。
    • @Jarod42 我们做到了,我们将如何指向 2 个相同类型的不同参照物?我们肯定需要分配内存吗?
    猜你喜欢
    • 2011-05-05
    • 2020-11-18
    • 1970-01-01
    • 1970-01-01
    • 2020-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多