减少对基础样板衍生委托在非虚拟多态类
考虑一个封闭类层次结构如下述:减少对基础样板衍生委托在非虚拟多态类
class B {...};
class D1 final : public B {...};
class D2 final : public B {...};
凡B
是一个抽象的基类和D1
和D2
是它的派生类。
由于实现约束或设计,没有这些类的具有任何virtual
方法,但成员函数上B
在D1
具有不同的实现并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 ... }
}
上。这里B
的to_string
方法只检查的B
最派生类型是D1
D2
或并调用适当的方法(也使用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(); }
通用拉姆达作品是否最终通过了D1
或D2
(因为两者具有to_string
方法) 。在C++ 11中,我没有看到用同样简洁的方式表达这一点。
任何想法?当然,你可以使用宏来复制一个非泛型的宏,并将它传递给一个双参数的delegate
方法(对D1
和D2
采用不同的函子),但我想避免使用宏。
这里关闭意味着该组B
派生类的固定和在运行时是已知的。
摘要在概念但不是在 “纯virtual
” 感。那就是这个类不应该直接实例化 - 唯一有意义的整个对象是它的派生类。各种构造函数都制作为protected
来执行此操作。
这个怎么样?
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)();
}
看起来不错。 'delegate'也可以被扩展,以便将其他参数完美地转发给派生函数,假设其中一些可能有参数。 – aschepler
这不是一个答案,这是一个可憎的。但我认为,因为
- 的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旁边的标题中,您必须创建额外的宏来声明委托人与定义他们。
我是否正确拒绝了基于预处理器宏的解决方案? – lockcmpxchg8b
@ lockcmpxchg8b - 正确。事实上,我已经在使用这样的解决方案,因为编写'DELEGATE(x)'宏很容易,所以'DELEGATE(std :: string,to_string)'简单地扩展到'B :: to_string()'实现如上所示,然后一些其他的宏来处理带参数的函数等。 – BeeOnRope
@BeeOnRope - 我完全误解了这个问题;抱歉。 – max66