如何管理 std::list 元素作为引用

How to manage std::list elements as references?

本文关键字:list 元素 引用 std 何管理 管理      更新时间:2023-10-16

我有以下代码:

struct Foo {
    int var1;
    int var2;

    friend std::ostream& operator<<(std::ostream& os, const Foo& s){
        return os << "[Foo] " << s.var1 << "," <<  s.var2 ;
    }
};
int main() {
    Foo foo;
    foo.var1 = 1;
    foo.var2 = 2;
    std::list<Foo> list;
    list.push_back(foo);
    Foo &foo2 = list.front();
    foo2.var2 = 5;
    std::cout << "foo (" << &foo << "): " << foo << std::endl;
    std::cout << "foo2 (foo from list) (" << &list.front() << "): " << foo2 << std::endl;
}

我希望foofoo2都引用同一个对象。因此,当我将5分配给foo2.var2时,我也想修改foo.var2。然而,正如我们在以下输出中看到的那样,这并没有发生:

foo (0x7fffffffe140): [Foo] 1,2
foo2 (foo from list) (0x61ac30): [Foo] 1,5 

正确的方法是什么?

使用 push_back 将元素插入列表时,push_back会创建一个插入到列表中的副本。一种解决方案是改用std::reference_wrapper作为列表的基础类型,例如

std::list<std::reference_wrapper<Foo>> lst;

然后像

lst.push_back(foo);

这是一个超级简单的例子,向您展示它是如何工作的:

#include <functional>
#include <iostream>
#include <list>
int main() 
{
    int i = 42;
    std::list<std::reference_wrapper<int>> lst;
    lst.push_back(i);           // we add a "reference" into the list
    lst.front().get() = 10;     // we update the list
    std::cout << i;             // the initial i was modified!
}

住在科里鲁

您需要reference_wrapper,因为您不能简单地创建引用列表,例如 std::list<Foo&> .或者,您可以使用指针,但我发现reference_wrapper方法更透明。

在上面的简单示例中,请注意需要使用std::reference_wrapper::get()来获取基础引用,因为reference_wrapper位于赋值运算符的左侧,因此不会通过 [ std::reference_wrapper::operator T& 隐式转换为 int

以下是修改为使用 reference_wrapper s 的完整工作代码:

http://coliru.stacked-crooked.com/a/fb1fd67996d6e5e9

您的std::list<Foo>包含实际的Foo对象。 当你调用list.push_back(foo)时,它会复制foo 然后,您将声明foo2引用该复制的对象,而不是原始对象。 这就是为什么foo在更改foo2成员时不会更新的原因。

尝试改用指针:

int main() {
    Foo foo;
    foo.var1 = 1;
    foo.var2 = 2;
    std::list<Foo*> list;
    list.push_back(&foo);
    Foo *foo2 = list.front();
    foo2->var2 = 5;
    std::cout << "foo (" << &foo << "): " << foo << std::endl;
    std::cout << "foo from list (" << foo2 << "): " << *foo2 << std::endl;
}

为此,您需要引用语义而不是值语义。实现引用语义的最直接方法是通过指针。只有示例中的一些小更改才能做到这一点:

struct Foo {
    int var1;
    int var2;
    friend std::ostream& operator<<(std::ostream& os, const Foo& s){
        return os << "[Foo] " << s.var1 << "," << s.var2 ;
    }
};
int main() {
    auto foo = std::make_unique<Foo>();
    
    foo->var1 = 1;
    foo->var2 = 2;
    // We want a non owning observer of foo
    auto& foo2 = *foo;
    std::list<std::unique_ptr<Foo>> list;
    
    // The ownership of foo is moved into the container
    list.emplace_back(std::move(foo));
    // Another non owning observer
    auto& foo3 = *list.front();
    foo3.var2 = 5;
    // foo is null here, since we moved it in the container, but we have foo2
    std::cout << "foo (" << &foo2 << "): " << foo2 << std::endl;
    std::cout << "foo from list (" << list.front().get() << "): " << foo3 << std::endl;
}

如果将输出更改为以下内容:

std::cout << "foo " << foo << std::endl;
std::cout << "front " << list.front() << std::endl;
std::cout << "foo2 " << foo2 << std::endl;

你会发现输出如下所示:

foo [Foo] 1,2
front [Foo] 1,5
foo2 [Foo] 1,5

事实上,当你得到一个参考,然后设置foo2,你确实改变了列表的前面! 令人惊讶的是,列表的前面不再等于foo。 当您将某些内容推送到列表中时,它会在该列表中放置一个副本,而不是引用。