虚拟调度后调用基本成员(模拟类似虚拟析构函数的调度)

时间:2015-09-18 10:18:42

标签: c++

虚拟调度只需选择"右键"要在层次结构中调用的东西。

我想实现一个类似于析构函数行为的功能,其中首先调用派生的析构函数,然后是层次结构中所有析构函数,直到最顶层。

客户端应该只定义成员函数,并且应该自动解决调用顺序。我有一个使用CRTP而没有虚拟呼叫的草案解决方案;它有其优点和缺点:

#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(); 
}

Demo

这样用户只需从调度程序派生并定义她感兴趣的方法(这意味着调度程序定义了所需的所有接口)

在缺点方面,引入了CRTP的所有缺点,我并没有真正的虚拟调度。有没有更简洁和惯用的方式来实现这一目标?它可以用于更深层次的层次结构吗?

修改

我有一个重置方法,我希望调用Derived::reset来触发Base1::reset最多BaseN::reset的调用,而无需用户实际写入这些函数的调用,就像对析构函数的调用将触发调用析构函数的层次结构。

3 个答案:

答案 0 :(得分:2)

你想为某些功能模仿类似析构函数的行为。

模仿析构函数行为的最明智方法是使用析构函数。

我的意思是在类的某些并行层次结构的析构函数中调用这些函数。

首先你需要知道基类 - 我试图使用__direct_bases - gcc的新扩展 - 但如果失败(gcc bug),那么让我们使用C ++解决方案:

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

以上要求此层次结构中的每个类都必须具有Base typedef - 可以在将来的C ++版本中解决。

所以有了基类 - 我们可以创建两个层次结构 - 一个用于非const,一个用于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 link

这个框架可以扩展。 例如。多基类可以这种方式完成:

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

Ideone link

以非虚拟方式调用虚函数的问题可以在lambda级别上解决。

使用这个object_class_t类模板,我们可以得到一个对象的“原始”类类型:

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

因此,假设我们有任何重置功能虚拟 - 并希望以非虚拟方式调用它们(所需行为):

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);
}

答案 1 :(得分:2)

感谢std::tr2::direct_bases,这实际上非常简单明了。希望这将在未来成为一些C ++标准,但是现在以下示例实际上是在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 :(得分:1)

这种方法不会侵入类的主体 在你的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>的等级子项。

基类,例如great_grandchild reset, 作为层次结构中的链接,用于封装向上递归调用 grandchild。它将childgreat_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();   
}

您可以附加以下内容来制作说明性程序:

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()

应该是outpout:

obj5

请注意,在base的情况下,哪个层次类型无关紧要 - 不一定是reset - 链接base::reset()被调用:如果它完全递归 它将一直递归到obj6

请注意resetchain<P>上对某个对象调用chain<P,C>的情况 动态类型P::reset()而不是{{1}}会抑制递归:它是 相当于调用{{1}}。

在每个中支持多个链式函数的进一步问题 从那以后,层次结构类逐渐陷入支持一个问题的阶段 一个人可以调用任何其他所需的成员函数。同样 将参数传递给链式函数的问题会崩溃 将参数元组传递给一个链式函数的问题。

(g ++ 5.1 / clang ++ 3.6,C ++ 14,非常适合C ++ 11)