派生类型的成员
Member of derived type
考虑以下示例:
- 我有一个抽象 class
socket
,它为一个很好的面向对象的套接字定义了一些虚拟接口(例如,send
和 receive
)。
- 然后 class 以多种方式专门化,(例如,在我的情况下,我可以有一个
TCP
套接字或一个 UDT
套接字,它们以不同的方式工作(例如,UDT
基于底层的 UDP
套接字)但它们具有相同的接口,即可以 send
和 receive
.
- 我有一个
connection
class 应该以某种方式包装一个套接字,添加一大堆功能(例如发送和接收对象的模板方法,它序列化它们,一切都很漂亮)。
现在,我在 connection
中可以做的最简单的事情就是拥有一个 socket *
指针。 connection
然后可以在其构造函数中接受一个指针,一切都很好。但是,这里存在一个性能问题:每当我的 connection
对象想要与我的 socket
对象通信时,就会有一个需要时间的取消引用。
我想做的是让 socket
真正成为 connection
的成员。不要误会我的意思:我知道 socket
的不同特化可以有不同的大小,所以我不能简单地做这样的事情:socket my_pretty_socket;
我的想法
我想知道的是,如果下面的想法没有任何意义,为什么。
我有一组有限的 socket
可能的专业化。如果我构建一些 template any <typename base, typename... specializations>
会怎样:
- 分配与最大的特化一样多的
char
数组。
- 提供一个模板构造函数,将其参数转发给指定特化的构造函数,并在分配的数组上使用放置
new
来创建对象。
- 表现得像对
socket
的引用,例如通过提供 operator * ()
或将每个对 socket
接口的调用转发到它正在存储的特化。
通过这种方式,我可以将对象作为 connection
的成员放置到位,并且可以在节省内存事务的同时保留接口的所有优良属性。
- 这个想法很蠢吗?为什么?
- 是否已经有解决方案可以做到这一点?我相信我不会是唯一遇到这种问题的人。
根据问题和评论,我认为最好的办法是简单地放弃继承并使用 variant
。该解决方案需要 boost、C++17 或带有变体 TS 实现的新式 compiler/standard 库,最好是 14。变体是一种存储多种类型之一的类型。它根据最大类型的大小将所有内容存储在堆栈中。
struct tcp {
void do_stuff() const;
};
struct udp {
void do_stuff() const;
};
using socket_type = std::variant<tcp,udp>;
void do_stuff(const socket_type& s) {
visit([] (const auto& x) { x.do_stuff(); }, s);
}
enum class SocketKind { TCP, UDP };
template <class ... Args>
socket_type make_socket(SocketKind k, Args && args) {
if (k == SocketKind::TCP) { return tcp(std::forward<Args>(args)...); }
if (k == SocketKind::UDP) { return udp(std::forward<Args>(args)...); }
}
您可以使用这样的工厂创建这些套接字,然后您可以在套接字对象上一般调用 do_stuff
。所有内容都在线存储在内存中,并且将通过 switch-case 处理分派,如果您有少量套接字类型,这可能比 vtable 更快(但这只是猜测)。
(请注意,我使用可变 lambda 来编写 do_stuff
,这只是简化了事情,但并不是真正需要的。您可以手动写出该访问者,因此您仍然可以使用此解决方案甚至C++03只要能用boost即可)。
考虑以下示例:
- 我有一个抽象 class
socket
,它为一个很好的面向对象的套接字定义了一些虚拟接口(例如,send
和receive
)。 - 然后 class 以多种方式专门化,(例如,在我的情况下,我可以有一个
TCP
套接字或一个UDT
套接字,它们以不同的方式工作(例如,UDT
基于底层的UDP
套接字)但它们具有相同的接口,即可以send
和receive
. - 我有一个
connection
class 应该以某种方式包装一个套接字,添加一大堆功能(例如发送和接收对象的模板方法,它序列化它们,一切都很漂亮)。
现在,我在 connection
中可以做的最简单的事情就是拥有一个 socket *
指针。 connection
然后可以在其构造函数中接受一个指针,一切都很好。但是,这里存在一个性能问题:每当我的 connection
对象想要与我的 socket
对象通信时,就会有一个需要时间的取消引用。
我想做的是让 socket
真正成为 connection
的成员。不要误会我的意思:我知道 socket
的不同特化可以有不同的大小,所以我不能简单地做这样的事情:socket my_pretty_socket;
我的想法
我想知道的是,如果下面的想法没有任何意义,为什么。
我有一组有限的 socket
可能的专业化。如果我构建一些 template any <typename base, typename... specializations>
会怎样:
- 分配与最大的特化一样多的
char
数组。 - 提供一个模板构造函数,将其参数转发给指定特化的构造函数,并在分配的数组上使用放置
new
来创建对象。 - 表现得像对
socket
的引用,例如通过提供operator * ()
或将每个对socket
接口的调用转发到它正在存储的特化。
通过这种方式,我可以将对象作为 connection
的成员放置到位,并且可以在节省内存事务的同时保留接口的所有优良属性。
- 这个想法很蠢吗?为什么?
- 是否已经有解决方案可以做到这一点?我相信我不会是唯一遇到这种问题的人。
根据问题和评论,我认为最好的办法是简单地放弃继承并使用 variant
。该解决方案需要 boost、C++17 或带有变体 TS 实现的新式 compiler/standard 库,最好是 14。变体是一种存储多种类型之一的类型。它根据最大类型的大小将所有内容存储在堆栈中。
struct tcp {
void do_stuff() const;
};
struct udp {
void do_stuff() const;
};
using socket_type = std::variant<tcp,udp>;
void do_stuff(const socket_type& s) {
visit([] (const auto& x) { x.do_stuff(); }, s);
}
enum class SocketKind { TCP, UDP };
template <class ... Args>
socket_type make_socket(SocketKind k, Args && args) {
if (k == SocketKind::TCP) { return tcp(std::forward<Args>(args)...); }
if (k == SocketKind::UDP) { return udp(std::forward<Args>(args)...); }
}
您可以使用这样的工厂创建这些套接字,然后您可以在套接字对象上一般调用 do_stuff
。所有内容都在线存储在内存中,并且将通过 switch-case 处理分派,如果您有少量套接字类型,这可能比 vtable 更快(但这只是猜测)。
(请注意,我使用可变 lambda 来编写 do_stuff
,这只是简化了事情,但并不是真正需要的。您可以手动写出该访问者,因此您仍然可以使用此解决方案甚至C++03只要能用boost即可)。