我希望在
std::reference_wrapper<MyType>
上自动将 MyType&
推断为 bool operator<(
。与方法不匹配。但是当我添加额外的 bool operator<(
方法时,代码正在编译。我是不是错过了什么?
#include <iostream>
#include <set>
#include <functional>
class MyType {
public:
bool operator<(const MyType& target) const {
return this < ⌖
}
};
// it doesn't compile if remove the method below.
bool operator<(const std::reference_wrapper<MyType>& a, const MyType& b) {
return a.get() < b;
}
int main(int argc, char* argv[]) {
std::set<std::reference_wrapper<MyType>> types;
MyType t1, t2, t3;
types.insert(std::ref(t1));
types.insert(std::ref(t2));
types.insert(std::ref(t3));
types.insert(std::ref(t1));
std::cout << "size: " << types.size() << std::endl;
return 0;
}
添加比较器就解决了问题。
std::set<std::reference_wrapper<MyType>> types;
到
std::set<std::reference_wrapper<MyType>, std::less<MyType>> types;
std::less
比较函子在这里不起作用。原因是它使用 MyType
类型引用的 content而不是对
MyType
类型变量的引用作为集合的索引。试试这个:
template<typename T>
void printReferenceContainer(T &container) {
std::cout << "content of container:" << std::endl;
for (auto item: container) {
std::cout << "item: " << item.get() << std::endl;
}
std::cout << "done" << std::endl;
std::cout << std::endl;
}
void main() {
std::set<std::reference_wrapper<std::string>, std::less<std::string>> container;
std::string a = "val 1";
std::string b = "val 2";
std::string c = "val 2"; // same value as b
std::string d = "val 4";
container.emplace(a);
container.emplace(b);
container.emplace(c);
container.emplace(d);
printReferenceContainer(container);
a = "val 10";
b = "val 11";
c = "val 12";
d = "val 13";
printReferenceContainer(container);
}
虽然期望集合中有 4 个值,但因为添加了 4 个不同变量的引用,所以只有 3 个,因为变量
b
和 c
的值相同,导致 c
没有被添加,因为它的值已经包含在集合中:
content of container:
item: val 1
item: val 2
item: val 4
done
content of container:
item: val 10
item: val 11
item: val 13
done
std::less
函子实际上并不采用reference进行比较,而是采用其content进行比较,通过使用隐式类型转换运算符到MyType
,在本例中为std::string
。
为了解决这个问题,有必要仔细研究
std::reference_wrapper
的真正作用。引用实际上并不以占用内存的方式存在。以一种非常简单的方式,它们只是为其分配的变量的编译器别名。请参阅 https://isocpp.org/wiki/faq/references 了解更详细的说明。
std::reference_wrapper
通过保存指向它引用的变量的内部指针来模仿这种行为。它提供了所有必要的成员函数,如隐式构造函数、赋值运算符、类型转换运算符等,以像普通编译器生成的引用一样进行act。但事实上它只是一个保存指针的结构体。因此,它可以存储在标准容器中,而不是真正的引用中。缺点是它没有比较运算符,例如小于,可以比较引用本身。
由于 std::set
仅存储唯一值,其唯一性和顺序是通过使用
小于运算符确定的,因此它不能简单地存储
std::reference_wrapper
。它需要帮助,通过提供比较函子。使用 std::less
时,std::reference_wrapper
现在完全按照预期执行操作。它的行为就像对其最初分配的变量的真实引用,因此
std::less
函子会比较引用的content,如上所述,导致不良行为。 解决方案是提供一个自定义比较函子,用于比较
std::reference_wrapper
本身,而不是其内容。它可以通过比较
std::reference_wrapper
结构的内部指针来做到这一点。由于内部指针很可能是私有成员,因此可以使用 take address运算符来获取其值,这当然是最初分配的变量的地址。 有了这个
ref_less
比较函子,它就可以工作了:
template<typename T>
struct ref_less {
// use the 'take address' operator of the reference_wrapper for comparison
bool operator()(const T &x, const T &y) const { return &x < &y; }
};
void main() {
std::set<std::reference_wrapper<std::string>, ref_less<std::string>> container;
std::string a = "val 1";
std::string b = "val 2";
std::string c = "val 2"; // same value as b
std::string d = "val 4";
container.emplace(a);
container.emplace(b);
container.emplace(c);
container.emplace(d);
printReferenceContainer(container);
a = "val 10";
b = "val 11";
c = "val 12";
d = "val 13";
printReferenceContainer(container);
}
这次可以看到它保存了所有变量:
content of container:
item: val 1
item: val 2
item: val 2
item: val 4
done
content of container:
item: val 10
item: val 11
item: val 12
item: val 13
done
仍然有一个缺点或者更好的可能意想不到的行为。由于
std::set
按给定函子排序,因此集合排序顺序由引用变量的
address确定,而不是由它们的 content 确定。因此,排序顺序毫无意义,并且可能依赖于编译器。当然,无论如何这都是没有意义的,因为内容随时可能改变,而集合却无法知道。 如果首先定义变量
d
,则使用此编译器实现,它会首先排序:
std::string d = "val 4";
std::string a = "val 1";
std::string b = "val 2";
std::string c = "val 2"; // same value as b
输出为:
content of container:
item: val 4
item: val 1
item: val 2
item: val 2
done
content of container:
item: val 13
item: val 10
item: val 11
item: val 12
done
因此,这种容器可能有非常特殊的用途,即它保存一堆对变量的引用,并确保一个变量不能被添加两次,但会失去集合的排序属性。