Call the base element after virtual dispatch (emulate virtual destructive dispatch)

Virtual dispatch simply selects the “right” thing to call in the hierarchy.

I want to implement functionality similar to the behavior of destructors, where the derived destructor is called first, and then all the destructors along the hierarchy to the very top base.

The client should simply define a member function, and the order of calls should be resolved automatically. I have a design solution using CRTP and no virtual calls; It has its advantages and disadvantages:

#include <iostream>
#include <memory>

using namespace std;

template<class T>
struct Dispatcher
{
    void f() {
        ((T*)this)->f();
        std::cout << "calling f base\n"; 
    }
};

struct Implementation : Dispatcher<Implementation>
{
    void f() {
        std::cout << "calling f derived\n"; 
    }
};

int main()
{
    shared_ptr<Dispatcher<Implementation>> obj = make_shared<Implementation>(); 
    obj->f(); 
}

Thus, the user simply displays from the dispatcher and determines the method of interest to him (this means that the dispatcher defines all the necessary interface)

CRTP , . ? ?

reset, , Derived::reset Base1::reset BaseN::reset , .

+4
3

std::tr2::direct_bases . , , ++, gcc.

-, . std::tr2::__reflection_typelist T:

template <typename T>
using direct_bases_t = typename std::tr2::direct_bases<T>::type;

, :

struct A { };
struct B : A { };
struct C : B { };

A , C , B. ( direct_bases, , . ). , , :

template <typename T, typename F, typename... Bases>
void for_each_base(T* t, F f, std::tr2::__reflection_typelist<Bases...> )
{
    using expander = int[];
    expander{0,
        (void(
            for_each_base(static_cast<Bases*>(t), f)
        ), 0)...
    };
}

template <typename T, typename F>
void for_each_base(T* t, F f)
{
    f(t);
    for_each_base(t, f, direct_bases_t<T>{});
}

f , . , :

struct Logger {
    template <typename T>
    void operator()(T* ) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    C c;
    for_each_base(&c, Logger{});
}

:

void Logger::operator()(T*) [with T = C]
void Logger::operator()(T*) [with T = B]
void Logger::operator()(T*) [with T = A]

Demo

:

for_each_base(obj, [](auto p){ p->reset(); });

direct_bases . - :

template <typename... > struct typelist { };

struct A { using bases = typelist<>; };
struct B : A { using bases = typelist<A>; };
struct C : B { using bases = typelist<B>; };

template <typename T>
using direct_bases_t = typename T::bases;

( typelist std::tr2::__reflection_typelist).

+2

- .

- .

.

- __direct_bases - gcc - (gcc bug), ++-:

template <typename T>
using GetBaseType = typename T::Base;
using NoBase = void;

, Base typedef - ++.

, - : , const:

template <typename T>
class CallUp;

template <>
class CallUp<NoBase>
{
public:
   template <typename Op, typename T>
   CallUp(Op&& ignoreOperation, T&& ignoreObject) {}
};


template <typename T>
class CallUp : public CallUp<GetBaseType<T>>
{
public:
    template <typename Op>
    CallUp(Op&& op, T& obj) : CallUp<GetBaseType<T>>(op, obj), op(std::forward<Op>(op)), obj(obj)
    {}
    ~CallUp()
    {
        op(obj);
    }
private:
    std::function<void(T&)> op;
    T& obj;
};

template <typename T>
class ConstCallUp;

template <>
class ConstCallUp<NoBase>
{
public:
   template <typename Op, typename T>
   ConstCallUp(Op&& ignoreOperation, T&& ignoreObject) {}
};

template <typename T>
class ConstCallUp : public ConstCallUp<GetBaseType<T>>
{
public:
    template <typename Op>
    ConstCallUp(Op&& op, const T& obj) : ConstCallUp<GetBaseType<T>>(op, obj), op(std::forward<Op>(op)), obj(obj)
    {}
    ~ConstCallUp()
    {
        op(obj);
    }
private:
    std::function<void(T&)> op;
    const T& obj;
};

template <typename Op, typename T>
auto callUp(Op&& op, T& obj)
{
    return CallUp<T>(std::forward<Op>(op), obj);
}
template <typename Op, typename T>
auto callUp(Op&& op, const T& obj)
{
    return ConstCallUp<T>(std::forward<Op>(op), obj);
}

callUp .

- - .

. :

class Foo0
{
public:
    using Base = NoBase;
    void reset()
    {
        std::cout << "reset Foo0\n";
    }
    void print() const
    {
        std::cout << "Foo0\n";
    }
};

class Foo1 : public Foo0
{
public:
    using Base = Foo0;
    void reset()
    {
        std::cout << "reset Foo1\n";
    }
    void print() const
    {
        std::cout << "Foo1\n";
    }
};

class Foo2 : public Foo1
{
public:
    using Base = Foo1;
    void reset()
    {
        std::cout << "reset Foo2\n";
    }
    void print() const
    {
        std::cout << "Foo2\n";
    }
};


int main() {
    Foo2 foo2;
    callUp([](auto&& obj){obj.reset();}, foo2);
    callUp([](auto&& obj){obj.print();}, foo2);
}

:

reset Foo2
reset Foo1
reset Foo0
Foo2
Foo1
Foo0

ideone.


. . :

template <typename ...T>
struct MultiBases {};

template <typename ...T>
class CallUp<MultiBases<T...>> : public CallUp<T>...
{
public:
    template <typename Derived, typename Op>
    CallUp(Op&& op, Derived& obj) : CallUp<T>(op, obj)...
    {}
};
template <typename ...T>
class ConstCallUp<MultiBases<T...>> : public ConstCallUp<T>...
{
public:
    template <typename Derived, typename Op>
    ConstCallUp(Op&& op, Derived& obj) : ConstCallUp<T>(op, obj)...
    {}
};

:

class Foo01
{
public:
    using Base = NoBase;
    void reset();
    void print() const;
};

class Foo02
{
public:
    using Base = NoBase;
    void reset();
    void print() const;
};


class Foo1 : public Foo01, public Foo02
{
public:
    using Base = MultiBases<Foo01, Foo02>;
    void reset();
    void print() const;
};

class Foo2 : public Foo1
{
public:
    using Base = Foo1;
    void reset();
    void print() const;
};


int main() {
    Foo2 foo2;
    callUp([](auto&& obj){obj.reset();}, foo2);
    callUp([](auto&& obj){obj.print();}, foo2);
}

:

reset Foo2
reset Foo1
reset Foo02
reset Foo01
Foo2
Foo1
Foo02
Foo01


.

object_class_t "raw" :

template <typename T>
using object_class_t = std::remove_cv_t<std::remove_reference_t<T>>;

, , reset virtual - (, ):

int main() {
    Foo2 foo2;
    auto nonVirtualReset = [](auto&& obj)
    {
         using ObjClass = object_class_t<decltype(obj)>;
         obj.ObjClass::reset();
    };
    callUp(nonVirtualReset, foo2);
    callUp([](auto&& obj){obj.print();}, foo2);
}
+2

, hiererchy, , :

template<class Parent, class Child = void> struct chain;

: , :

struct base 
{ ... virtual void reset(); ... };
struct child : base 
{ ... virtual void reset(); ... };
struct grandchild : child 
{ ... virtual void reset(); ... };
struct great_grandchild : grandchild 
{ ... virtual void reset(); ... };
struct great_great_grandchild : great_grandchild 
{ ... virtual void reset(); ... };

:

struct base 
{ ... virtual void reset(); ... };
struct child : chain<base> 
{ ... virtual void reset(); ... };
struct grandchild : chain<base,child> 
{ ... virtual void reset(); ... };
struct great_grandchild : chain<child,grandchild> 
{ ... virtual void reset(); ... };
struct great_great_grandchild : chain<grandchild,great_grandchild> 
{ ... virtual void reset(); ... };

, , .

- , "" - - - - chain<P> chain<P,C>, C - P.

, chain<grandchild,child> of great_grandchild, , reset. grandchild, child - great_grandchild. , great_grandchild this->grandchild::reset() this->child::reset(), child. chain<T>: :

struct child : chain<base> { ... };

chain

#include <type_traits>

namespace detail {

    template<class ...> using void_t = void;

    template<class, class = void >
    struct has_base_type : std::false_type {};

    template<class T >
    struct has_base_type<T,void_t<typename T::base_type>> : std::true_type
    {
        using type = typename T::base_type;
    };

    template<class T>
    using has_base_type_t = typename has_base_type<T>::type;

    template<class T>
    struct has_real_base
    {
        static constexpr bool value = has_base_type<T>::value && 
            !std::is_same<has_base_type_t<T>,T>::value;
    };

    template<class Parent, class Child = void>
    struct chain : virtual Parent, virtual Child
    {
        using base_type = Parent;
        using child_type = Child;

        template<class T>
        static std::enable_if_t<has_real_base<T>::value>
        recurse(chain * const pd) {
            pd->base_type::base_type::reset();
        }

        template<class T>
        static std::enable_if_t<!has_real_base<T>::value> 
        recurse(chain * const pd) {}

        void reset() override {
            this->child_type::reset();
            this->base_type::reset();
            recurse<base_type>(this);
        }
        virtual ~chain() = default;
    };

    template<class T>
    struct chain<T> : virtual T
    {
        using base_type = T;
        void reset() override {
            this->base_type::reset();
        }
        virtual ~chain() = default;
    };

} // namespace detail

template<class Parent, class Child = void>
struct chain : detail::chain<Parent,Child>
{
    using base_type = detail::chain<Parent,Child>;
    void reset() override {
        this->base_type::reset();
    }
    virtual ~chain() = default;
};

:

#include <iostream>
#include <memory>

struct base
{
    virtual void reset() {
        std::cout << "calling base::reset()\n"; 
    }
    virtual ~base() = default;
};

struct child : virtual chain<base>
{
    void reset() override {
        std::cout << "calling child::reset()\n"; 
    }
    virtual ~child() = default;
};

struct grandchild : chain<base,child>
{
    void reset() override {
        std::cout << "calling grandchild::reset()\n";
    }
    virtual ~grandchild() = default;
};

struct great_grandchild : chain<child,grandchild>
{
    void reset() override {
        std::cout << "calling great_grandchild::reset()\n";
    }
    virtual ~great_grandchild() = default;
};

struct great_great_grandchild : chain<grandchild,great_grandchild>
{
    void reset() override {
        std::cout << "calling great_great_grandchild::reset()\n";
    }
    virtual ~great_great_grandchild() = default;
};

using namespace std;

int main()
{
    cout << "obj1\n";
    shared_ptr<base> obj1 = make_shared<chain<base,child>>(); 
    obj1->reset();
    cout << "\n";
    cout << "obj2\n";
    shared_ptr<base> obj2 = make_shared<chain<child,grandchild>>();
    obj2->reset();
    cout << "\n";
    cout << "obj3\n";
    shared_ptr<base> obj3 = make_shared<chain<grandchild,great_grandchild>>();
    obj3->reset();
    cout << "\n";
    cout << "obj4\n";
    shared_ptr<base> obj4 = 
        make_shared<chain<great_grandchild,great_great_grandchild>>();
    obj4->reset();
    cout << "\n";
    cout << "obj5\n";    
    shared_ptr<grandchild> obj5 = make_shared<chain<grandchild,great_grandchild>>();
    obj5->reset();
    cout << "\n";
    cout << "obj6\n";    
    shared_ptr<base> obj6 = make_shared<chain<grandchild>>();
    obj6->reset();   
}

outpout:

obj1
calling child::reset()
calling base::reset()

obj2
calling grandchild::reset()
calling child::reset()
calling base::reset()

obj3
calling great_grandchild::reset()
calling grandchild::reset()
calling child::reset()
calling base::reset()

obj4
calling great_great_grandchild::reset()
calling great_grandchild::reset()
calling grandchild::reset()
calling child::reset()
calling base::reset()

obj5
calling great_grandchild::reset()
calling grandchild::reset()
calling child::reset()
calling base::reset()

obj6
calling grandchild::reset()

, obj5 , - base - reset : base::reset().

Note that in the case obj6, calling resetthe dynamic type on the object chain<P>, and not chain<P,C>, suppresses recursion: this is equivalent to a call P::reset().

A further problem of supporting several chain functions in each of the hierarchical classes is collapsing into a support problem, since any other member functions can be called. also the problem of passing arguments to chain functions is reduced to the problem of passing a tuple of arguments to one chain function.

(g ++ 5.1 / clang ++ 3.6, C ++ 14, trivially adaptable to C ++ 11)

+1
source

Source: https://habr.com/ru/post/1607993/


All Articles