在我之前写的一篇博客《使用decltype取函数类型遇到的“invalidly declared function type”问题》中,最近收到一条评论:
X: 想问问,既然模板里面已经传入了comp指针了,为什么构造函数里还要再传一次呢?
我先把情境描述的更清楚一些,std::priority_queue是优先队列的标准库实现,其声明如下:
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;
这个类有一堆不同签名的构造函数,不过我们只关心其中三个,前两个都是所谓的委托构造函数,依赖第三个构造函数对对象进行初始化:
priority_queue() : priority_queue(Compare(), Container()) { }
explicit priority_queue(const Compare& compare)
: priority_queue(compare, Container()) { }
explicit priority_queue( const Compare& compare = Compare(),
const Container& cont = Container() );
令X疑惑的是下面的代码:
struct Node
{
int value;
int ext;
static bool comp(const Node &a, const Node &b)
{
return a.value > b.value;
}
};
priority_queue<Node, vector<Node>, decltype(Node::comp) *> pq(Node::comp);
为什么在模板参数里面“传过”comp
指针了,还要在构造函数再传一次呢?其实只要搞清楚模板参数传入的到底是个什么东西,就不会有这个疑问了。
priority_queue
类中含有一个类型为Compare
的对象,用来对元素进行比较。第三个模板参数即为Compare
的类型,当第三个模板参数为decltype(Node::comp) *
时,Compare
是什么呢?
答案是bool (*)(const Node &, const Node &)
,因此以下这两种写法是等价的:
priority_queue<Node, vector<Node>, decltype(Node::comp) *> pq(Node::comp);
priority_queue<Node, vector<Node>, bool (*)(const Node &, const Node &)> pq(Node::comp);
它是一个函数指针,假如我们调用的是无参版本的构造函数会发生什么呢?答案是会得到一个野指针,它确实能通过编译,但运行时会发生什么情况就完全看天意了。
再来看看其他常见情况,Compare
还可以是一个std::less
这样的函数对象类型,或者是一个lambda
表达式类型,还可以是一个std::function
类型。
当Compare
是一个函数对象类型时,且含有无参的构造函数,那上面的几个构造函数都是能够正常使用的。但是如果Compare
所有构造函数含有参数,无法默认构造时,那上面第一个无参版本的构造函数就无法通过编译了。
struct NodeCompare0
{
bool operator()(const Node &a, const Node &b)
{
return a.value > b.value;
}
};
struct NodeCompare1
{
enum Order
{
Big,
Little
};
Order m_order;
NodeCompare1(Order o) : m_order(o) {}
bool operator()(const Node &a, const Node &b)
{
if (m_order == Big)
{
return a.value < b.value;
}
else
{
return a.value > b.value;
}
}
};
priority_queue<Node, vector<Node>, NodeCompare0> pq0; // OK
priority_queue<Node, vector<Node>, NodeCompare1> pq1; // error
priority_queue<Node, vector<Node>, NodeCompare1> pq2((NodeCompare1(NodeCompare1::Big))); // OK
当Compare
是一个lambda
表达式类型时,情况和函数对象类似,不同的地方在于:lambda
表达式即使不捕获任何变量(类似无参的函数对象),在C++20之前的版本也是不能默认构造的,但可以拷贝构造:
int main()
{
enum class Order
{
Big,
Little
};
Order order = Order::Big;
auto lambdaCompare0 = [](const Node &a, const Node &b)
{
return a.value > b.value;
};
auto lambdaCompare1 = [order](const Node &a, const Node &b)
{
if (order == Order::Big)
{
return a.value < b.value;
}
else
{
return a.value > b.value;
}
};
priority_queue<Node, vector<Node>, decltype(lambdaCompare0)> pq0; // error when -std=c++11 OK when -std=c++2a
priority_queue<Node, vector<Node>, decltype(lambdaCompare0)> pq1(lambdaCompare0); // OK
priority_queue<Node, vector<Node>, decltype(lambdaCompare1)> pq2; // error
priority_queue<Node, vector<Node>, decltype(lambdaCompare1)> pq3(lambdaCompare1); // OK
}
std::function
类型的情况和函数指针类似,不再赘述。
回到博客标题:为什么std::priority_queue
有一个构造函数接受Compare
类型对象作为参数?答案是:因为Compare
类型无法默认构造或者默认构造的结果不是我们期望的结果。