一种涉及比较函子的循环依赖关系
A circular dependency involving comparison functors
假设我们需要存储有关标记电子邮件的信息。每个消息都可以分配许多标签。此外,我们希望能够快速检索分配给给定标签的所有消息。这是我的设计:
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
的行(,则我们会遇到类似的问题,该标签集需要通过标签名称进行搜索。
对此有没有解决方案或更好的设计?
首先,一种将投影映射到排序中的方法:
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
是一个类型,当您使用它的operator()
时,它的任何实例都会调用println
。我们将指向-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
明确表示,我们正在通过在返回的std::string const&
s上调用GetMessageName
和<
进行订购,而没有任何开销。
这可以更简单、更直接地完成,但我个人喜欢我上面写的每一层洋葱(尤其是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
时,你都会喜欢它。
- C++GTKMM gui循环依赖关系
- 如何在头文件中声明类模板(由于循环依赖关系)
- C++模板方法中的循环依赖关系
- Wt::D bo 中的循环依赖关系
- 在包含窗口标头时难以解决循环依赖关系问题
- 解决循环依赖关系 c++ 的想法
- C++循环依赖关系,未声明的标识符
- C++ 中的循环依赖关系问题
- CMake 外部和内部静态库的循环依赖关系
- "std::shared_ptr"循环依赖关系是如何导致问题的
- 纯引用而不是weak_ptr来打破循环依赖关系
- "invalid use of incomplete type" .解决循环依赖关系
- C++循环依赖关系
- Cmake生成了VS项目的循环依赖关系,但没有制作文件.如何避免
- 有没有办法解决这个模板循环依赖关系
- 解决使用工厂时的循环依赖关系
- 如何解决此循环依赖关系
- 具有模板专用化的复合结构中的循环依赖关系
- 如果存在任何循环关系,我应该假设弱指针使用吗?
- Qt类及其成员之间的循环关系