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

时间:2017-05-24 17:36:54

标签: c++ reference

我有以下代码:

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 

这样做的正确方法是什么?

4 个答案:

答案 0 :(得分:16)

使用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!
}

Live on Coliru

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

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

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

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

答案 1 :(得分:4)

您的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;
}

答案 2 :(得分:2)

如果您将输出更改为:

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。当您将某些内容推送到列表中时,它会将副本放在该列表中,而不是引用。

答案 3 :(得分:2)

为此,您需要引用语义而不是值语义。实现引用语义最直接的方法是通过指针。您的示例中只会进行一些小的更改:

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