2017-11-25 285 views
2

考虑一个封闭类层次结构如下述:减少对基础样板衍生委托在非虚拟多态类

class B {...}; 
class D1 final : public B {...}; 
class D2 final : public B {...}; 

B是一个抽象的基类和D1D2是它的派生类。

由于实现约束或设计,没有这些类的具有任何virtual方法,但成员函数上BD1具有不同的实现并D2简单地通过使衍生的运行时检查委托给实际最派生的类型类型,如下所示:

class B { 
    bool isD1; 
protected: 
    B(bool isD1) : isD1{isD1} {} 
public: 
    std::string to_string() { 
    return isD1 ? static_cast<D1*>(this)->to_string() : static_cast<D2*>(this)->to_string(); 
    } 
} 

class D1 final : public B { 
public: 
    D1() : B(true) {} 
    std::string to_string() { // D1 specific implementation ... } 
} 

class D2 final : public B { 
public: 
    D2() : B(false) {} 
    std::string to_string() { // D2 specific implementation ... } 
} 

上。这里Bto_string方法只检查的B最派生类型是D1D2或并调用适当的方法(也使用C称为to_string ASES)。

很酷。

现在想象有另外10种方法,如B::to_string。我可以在C++ 11中做什么来减少B中的委托样板文件,而不使用宏?

在C++ 14它似乎是一个合理的做法是一个通用的代理机制,如:

class B { 
    ... 

    template <typename F> 
    auto delegate(F&& f) -> decltype(f(D1{})) { 
    return isD1 : f(*static_cast<D1*>(this)) : f(*static_cast<D2*>(this)); 
    } 

    std::string to_string() { 
    return delegate([](auto&& b){ return b.to_string(); }); 
    } 
} 

这里[](auto&& b){ return b.to_string(); }通用拉姆达作品是否最终通过了D1D2(因为两者具有to_string方法) 。在C++ 11中,我没有看到用同样简洁的方式表达这一点。

任何想法?当然,你可以使用宏来复制一个非泛型的宏,并将它传递给一个双参数的delegate方法(对D1D2采用不同的函子),但我想避免使用宏。


这里关闭意味着该组B派生类的固定和在运行时是已知的。

摘要在概念但不是在 “纯virtual” 感。那就是这个类不应该直接实例化 - 唯一有意义的整个对象是它的派生类。各种构造函数都制作为protected来执行此操作。

+0

我是否正确拒绝了基于预处理器宏的解决方案? – lockcmpxchg8b

+0

@ lockcmpxchg8b - 正确。事实上,我已经在使用这样的解决方案,因为编写'DELEGATE(x)'宏很容易,所以'DELEGATE(std :: string,to_string)'简单地扩展到'B :: to_string()'实现如上所示,然后一些其他的宏来处理带参数的函数等。 – BeeOnRope

+0

@BeeOnRope - 我完全误解了这个问题;抱歉。 – max66

回答

1

这个怎么样?

template <typename F1, typename F2> 
    auto delegate(F1 f1, F2 f2) -> decltype((D1{}.*f1)()) { 
    return isD1 ? (static_cast<D1*>(this)->*f1)() : (static_cast<D2*>(this)->*f2)(); 
    } 

    std::string to_string() { 
    return delegate(&D1::to_string, &D2::to_string); 
    } 

你也可以把它更强类型:

template <typename Result> 
    Result delegate(Result (D1::*f1)(), Result (D2::*f2)()) { 
    return isD1 ? (static_cast<D1*>(this)->*f1)() : (static_cast<D2*>(this)->*f2)(); 
    } 
+1

看起来不错。 'delegate'也可以被扩展,以便将其他参数完美地转发给派生函数,假设其中一些可能有参数。 – aschepler

1

这不是一个答案,这是一个可憎的。但我认为,因为

  • 的OP暗示他/她用一个更简单的基于宏的计划我会分享,并
  • 接近“零样板soluton”。

以下是一个工作示例。

#include <string> 
#include <sstream> 
#include <iostream> 
#include <delegate_macros> 

#define FOREACH_DELEGATE(A) \ 
    A(std::string, to_string,(),  ())\ 
    A(void,  setInt, (int a), (a))\ 

class B 
{ 
    DECLARE_VTAB_MEMBERS 
public: 
    B(DELEGATE_ARGS) : INITIALIZER_LIST { } 

    DEFINE_DELEGATORS 
}; 

class D1 : public B 
{ 
    int m_i; 
public: 
    D1() : B(PASS_DELEGATES) {} 
    void setInt(int i) {m_i = i;} 
    std::string to_string() {std::stringstream ss; ss << "D1:" << m_i; return ss.str();} 
}; 

class D2 : public B 
{ 
    int m_i; 
public: 
    D2() : B(PASS_DELEGATES) {} 
    void setInt(int i) {m_i = i * 5;} 
    std::string to_string() {std::stringstream ss; ss << "D2:" << m_i; return ss.str();} 
}; 

int main(int argc, char *argv[]) 
{ 
    D1 d1; 
    D2 d2; 

    B *ref = &d1; 

    ref->setInt(2); 
    std::cout << "((B*)&d1)->toString: " << ref->to_string() << std::endl; 

    ref = &d2; 
    ref->setInt(2); 
    std::cout << "((B*)&d2)->toString: " << ref->to_string() << std::endl; 
} 

息率

$ ./a.out 
((B*)&d1)->toString: D1:2 
((B*)&d2)->toString: D2:10 

的宏在delegate_macros是独立的B的结构和它的子类:

#define MAKE_DELEGATOR(ret, name, params, args)\ 
    ret name params\ 
    {\ 
    return (this ->* m_##name) args;\ 
    } 

#define MAKE_DELEGATE_REF(ret, name, params, args) (ret (B::*) params)&name, 

#define DECLARE_VTAB_MEMBER(t,n,p,a) t (B::*m_##n)p; 

#define MAKE_CTOR_INITIALIZER(t,n,p,a) m_##n(n), 
#define MAKE_CTOR_ARG(t,n,p,a) t (B::*n) p, 
#define MAKE_CTOR_PARAMS(t,n,p,a) t (B::*m_##n)p, 

#define DECLARE_VTAB_MEMBERS FOREACH_DELEGATE(DECLARE_VTAB_MEMBER) char dummy; 
#define INITIALIZER_LIST  FOREACH_DELEGATE(MAKE_CTOR_INITIALIZER) dummy() 
#define DEFINE_DELEGATORS FOREACH_DELEGATE(MAKE_DELEGATOR) 
#define DELEGATE_ARGS  FOREACH_DELEGATE(MAKE_CTOR_ARG) void * 
#define PASS_DELEGATES  FOREACH_DELEGATE(MAKE_DELEGATE_REF) NULL 

有AR我有几个理由称它为可憎的:

  • 它手动使VTABLE ...,如果你想要一个VTABLE,只需使用虚拟。
  • 它将简单的拼写转换为输出页面。
  • 它执行每个子类的指针到(B::*)变体的未经检查的转换。
    • 并且使用所谓的方法指针调用子类方法。
  • 它添加了虚拟构造函数参数和成员变量,使宏扩展更容易。
  • 没有人可以阅读它,除非他们花了几年时间写这种宏。
  • FOREACH_DELEGATE必须在您想要使用宏的每个翻译单元中定义,因此只有在B及其所有子类都在一个文件中定义时才适用。如果您想将B置于FOREACH_DELEGATE旁边的标题中,您必须创建额外的宏来声明委托人与定义他们。