涉及比较仿函数的循环依赖

时间:2015-07-22 14:11:03

标签: c++ c++11 circular-dependency incomplete-type

假设我们需要存储有关标记的电子邮件的信息。可以为每条消息分配许多标签。此外,我们希望能够快速检索分配给给定标签的所有消息。这是我的设计:

class Message;
class Label {
public:
    ...
private:
    std::string name_;
    std::set<std::shared_ptr<Message>, 
             std::function<bool(...)>> messages_; // Message is incomplete!
};

class Message {
public:
    ...
private:
    std::string title_;
    std::set<Label *, 
             std::function<bool(...)>> labels_; // fine
};

每个标签都存储分配标签的消息集。由于此集需要通过邮件标题进行搜索,因此我们会将std::function作为std::set的第二个模板参数进行比较。 问题:此功能对象需要能够访问Message的成员。但是,此时Message是不完整的类型。

通过将Message的定义放在Label的定义之前,无法解决这种情况,因为那时我们会遇到与传递给标签集的std::function类似的问题(在上面的代码中注释为fine的行,需要通过标签名称进行搜索。

是否有针对此的修复或更好的设计?

1 个答案:

答案 0 :(得分:0)

首先,将投影映射到排序的方法:

template<class F>
struct order_by_t {
  F f;
  using is_transparent = std::true_type;
  template<class Lhs, class Rhs>
  auto operator()(Lhs&& lhs, Rhs&& rhs)const
  -> decltype (
    static_cast<bool>(f(std::declval<Lhs>()) < f(std::declval<Rhs>())
  )
  {
    return f(std::forward<Lhs>(lhs)) < f(std::forward<Rhs>(rhs));
  }
};
template<class F>
order_by_t<std::decay_t<F>> order_by(F&& f) {
  return {std::forward<F>(f)};
}

投影采用类型X并将其“投影”到类型Y上。这里的技巧是类型Y是我们要通过以下方式排序X的字段的类型(在这种情况下,是一个字符串,投影将X改为X的名称。

这意味着我们所要做的就是定义投影(从我们的类型映射到我们想要按其排序的类型的部分),然后将其提供给order_by_t,它将生成一个为我们订购功能。

order_by_t似乎是有状态的,但并非如此。如果F是无国籍的,order_by_t也可以!无状态意味着我们不必初始化F,我们可以使用它,也可以使编译器更好地理解代码(跟踪状态对编译器来说很难,无状态的东西很容易优化)。

或者,简而言之,无国籍人比有状态更好。这是一个包装函数调用的无状态类型:

template<class Sig, Sig* f>
struct invoke_func_t;
template<class R, class...Args, R(*f)(Args...)>
struct invoke_func_t<R(Args...), f> {
  R operator()(Args...args)const {
    return f(std::forward<Args>(args)...);
  }
};

使用示例:

void println( std::string const& s ) {
  std::cout << s << '\n';
}
using printer = invoke_func_t< void(std::string const&), println >;

现在printer是一种类型,当您使用println时,它的任何实例都会调用operator()。我们将指针 - println存储在printer类型中,而不是将指针的副本存储在其中。这使得printer的每个实例都是无状态的。

接下来,包含函数调用的无状态order_by

template<class Sig, Sig* f>
struct order_by_f:
  order_by_t< invoke_func_t<Sig, f> >
{};

这是一行,上面的副作用很漂亮。

现在我们使用它:

class Message; class Label;

// impl elsewhere:
std::string const& GetMessageName( std::shared_ptr<Message> const& );
std::string const& GetLabelName( std::shared_ptr<Label> const& );

class Label {
private:
  std::string name_;
  using message_name_order = order_by_f<
      std::string const&(std::shared_ptr<Message> const&),
      GetMessageName
    >;
  std::set<std::shared_ptr<Message>, message_name_order > messages_;
};

我跳过了一堆箍,通过调用std::set并在返回的GetMessageName上调用<来清楚地告诉std::string const&我们要订购的内容,没有开销。

这可以更直接地完成,但我个人喜欢上面写的每个洋葱层(尤其是order_by)。

较短的版本:

class Message;
bool order_message_by_name( std::shared_ptr<Message> const&, std::shared_ptr<Message> const& );

class Label {
private:
  std::string name_;
  std::set<std::shared_ptr<Message>, 
         bool(*)(std::shared_ptr<Message>const&, std::shared_ptr<Message>const&)
   > messages_; // Message is incomplete!
  Label(std::string name):name_(std::move(name)),
    messages_(&order_messages_by_name)
  {}
};

我们在我们的集合中存储一个函数指针,告诉类如何订购它。

这有运行时间成本(编译器很难证明函数指针始终指向同一个函数,因此必须存储它并在每次调用时取消引用它),强制你写order_messages_by_name (一个丑陋的特定用途函数),并且有维护成本(你必须证明函数指针在你考虑那个集合时永远不会改变)。

另外,它并没有为您提供很酷的order_by功能,除了sort之外,您每次想std::vector <时都会喜欢它。