减少非虚拟多态类中从基委派到派生委派的样板
Reducing boilerplate for base to derived delegation in non-virtual polymorphic classes
考虑一个封闭的1类层次结构,如下所示:
class B {...};
class D1 final : public B {...};
class D2 final : public B {...};
其中B
是一个抽象的2基类,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
,并调用适当的方法(在这两种情况下也称为to_string
)。
凉。
现在想象一下还有 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(); });
}
}
在这里,无论最终传递D1
还是D2
,[](auto&& b){ return b.to_string(); }
通用 lambda 都有效(因为两者都有to_string
方法)。在C++11中,我没有看到同样简洁的方式来表达这一点。
有什么想法吗?
当然,您可以使用宏来复制非通用宏并将其传递给 2 参数delegate
方法(该方法为D1
和D2
使用单独的函子),但我想避免宏。
1这里的封闭意味着B
的派生类集是固定的,并且在运行时是已知的。
2概念抽象,但不是"纯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)();
}
这不是一个答案,这是一个可憎的东西。但我想我会分享,因为
- OP暗示他/她正在使用更简单的基于宏观的方案,并且
- 它接近"零样板解决方案"。
下面是一个工作示例。
#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
我称之为可憎之物有几个原因:
- 它手动制作一个 VTABLE...如果您想要一个 VTABLE,只需使用虚拟。
- 它将简单的拼写错误转换为输出页面。
- 它对每个子类指向
(B::*)
变体的方法的指针执行未经检查的强制转换。- 并使用指针到方法 so-cast 调用子类方法。
- 它添加了虚拟构造函数参数和成员变量,以使宏扩展更容易。
- 没有人能读懂它,除非他们花了数年时间写这种宏。
- FOREACH_DELEGATE必须在要使用宏的每个翻译单元中定义,因此仅当
B
及其所有子类都定义在一个文件中时才适用。 如果要将 B 与 FOREACH_DELEGATE 放在标题中,则必须创建其他宏来区分声明委托人与定义委托人。
- 为什么使用 "this" 指针调用派生成员函数?
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 在派生函数中指定void*参数
- 如何通过派生类函数更改基类中的向量
- 如何委托派生类使用其父构造函数?
- 如何使用单独文件中的派生类访问友元函数对象
- 派生类销毁的最佳实践是什么
- 如何使用基类指针引用派生类成员
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 存储模板类型以强制转换回派生<T>
- 需要从 istream 和 ostream 派生 iostream
- 在 C++ 中用派生类型重写成员函数
- 具有多个类、派生类的C++正向声明
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 如果基类包含双指针成员,则派生类的构造函数
- 为什么此派生对象无法访问基类的后递减方法?
- 减少非虚拟多态类中从基委派到派生委派的样板