【问题标题】:Member of derived type派生类型的成员
【发布时间】:2017-12-02 10:37:19
【问题描述】:

考虑以下示例:

  • 我有一个抽象类socket,它为一个不错的面向对象套接字定义了一些虚拟接口(例如,sendreceive)。
  • 然后该类以多种方式专门化,(例如,在我的情况下,我可以有一个TCP 套接字或UDT 套接字,它们以不同的方式工作(UDT 基于底层@987654327以@socket 为例),但它们具有相同的接口,即可以例如sendreceive
  • 我有一个 connection 类,它应该以某种方式包装一个套接字,添加很多功能(例如,发送和接收对象的模板方法,它序列化它们,一切都很漂亮)。

现在,我可以在connection 中做的最简单的事情就是拥有一个socket * 指针。 connection 然后可以在其构造函数中接受一个指针,一切都很好。但是,那里存在一个性能问题:每当我的 connection 对象想要与我的 socket 对象通信时,都会出现需要时间的取消引用。

我想做的是让socket 成为connection 的成员。不要误会我的意思:我知道,如果没有别的,socket 的不同专业可以有不同的大小,所以我不能简单地做这样的事情:socket my_pretty_socket;

我的想法

我想知道的是,如果下面的想法没有任何意义,以及为什么。

我有一组有限的socket 可能的专业化。如果我建立一些template any <typename base, typename... specializations> 会怎样:

  • 分配与最大专业化一样多的chars 数组。
  • 提供模板构造函数,将其参数转发到指定特化的构造函数,并使用分配数组上的放置new 来创建对象。
  • 行为类似于对socket 的引用,例如通过提供operator * () 或将每个调用转发到socket 的接口到它正在存储的专业化。

通过这种方式,我将对象作为connection 的成员就位,并且我将保留接口的所有良好属性,同时节省内存事务。

  1. 这个想法很愚蠢吗?为什么?
  2. 是否已有解决方案可以做到这一点?我确信我不会是唯一遇到此类问题的人。

【问题讨论】:

  • “是否已经有解决方案可以做到这一点?” boost::asio
  • 呃,但在您的解决方案中,您仍然必须取消引用。您仍然通过指向套接字的指针进行调用,因此您仍然通过 vtable。您唯一要更改的是数据的存储位置。这是你的意图吗?
  • 我的意思是,实际上最简单、最高效的做法就是完全摆脱继承,只需将connection 模板化为socket 的确切类型。然后您支付零间接成本,这比您尝试做的要简单得多。但是connection 现在的类型取决于它激活的套接字,因此您无法在运行时决定使用哪个连接(除非您在更高级别擦除类型)。这是个问题吗?
  • @NirFriedman 与网络 I/O 的成本相比?我认为不会。
  • 我在这里与@Barmar 在一起:缓存未命中,取消引用,与什么相比“慢”?这里我们讨论的是网络 I/O:与缓存未命中或“缓慢”取消引用相比,通过线路发送一个字节需要更多的时间。寻找干净且可维护的代码,阅读有关单一职责原则和关注点分离的信息。并且只有在衡量之后才开始担心性能。在大多数(不是全部)真实场景中,您对取消引用性能的担忧可以忽略不计。

标签: c++ memory abstract-class virtual


【解决方案1】:

基于问题+ cmets,我认为您最好的选择是简单地放弃继承并使用variant。此解决方案需要 boost、C++17 或具有变体 TS 的实现的新编译器/标准库,最好是 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)。

【讨论】:

  • 看起来正是我想要的。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-25
  • 2011-09-24
  • 2018-11-30
  • 1970-01-01
  • 2017-11-27
  • 2017-11-08
  • 2019-10-11
相关资源
最近更新 更多