将 std::reference_wrapper<MyType> 存储到 std::set

问题描述 投票:0回答:2

我希望在

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 < &target;
    }
};

// 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;
}
c++ c++11
2个回答
2
投票

添加比较器就解决了问题。

std::set<std::reference_wrapper<MyType>> types;

std::set<std::reference_wrapper<MyType>, std::less<MyType>> types;

0
投票

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

因此,这种容器可能有非常特殊的用途,即它保存一堆对变量的引用,并确保一个变量不能被添加两次,但会失去集合的排序属性。

© www.soinside.com 2019 - 2024. All rights reserved.