Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manage std::list elements as references?

Tags:

c++

reference

I have the following code:

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

I want both foo and foo2 to reference the same object. So when I assign 5 to foo2.var2, I would want to modify foo.var2 as well. Yet, as we can see in the following output this is not happening:

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

What would be the correct way to do that?

like image 529
jscherman Avatar asked May 24 '17 17:05

jscherman


People also ask

Is C++ list a linked list?

Advantages of the List in C++ A linked list is a dynamic data structure that can expand and shrink in size at runtime by allocating and deallocating memory. As a result, there is no need to specify the linked list's initial size.

Is std::list a linked list?

std::list. std::list is a container that supports constant time insertion and removal of elements from anywhere in the container. Fast random access is not supported. It is usually implemented as a doubly-linked list.

What does std :: ref do?

An std::reference_wrapper is a copyable and assignable object that emulates a reference. Contrary to its name, it does not wrap a reference. It works by encapsulating a pointer (T*) and by implicitly converting to a reference (T&).


2 Answers

When you use push_back to insert elements into a list, push_back creates a copy which is inserted into the list. A solution is to use a std::reference_wrapper instead as the underlying type of the list, like

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

and then push into it like

lst.push_back(foo);

Here is a super simple example that shows you how it works:

#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

You need the reference_wrapper since you cannot simply create a list of references, like std::list<Foo&>. Alternatively, you can use pointers, but I find the reference_wrapper approach more transparent.

In the simple example above note the need to use std::reference_wrapper::get() to obtain the underlying reference, as the reference_wrapper is on the left hand side of the assignment operator and hence won't be implicitly converted to int via [std::reference_wrapper::operator T&.

Below is your full working code modified to use reference_wrappers:

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

like image 109
vsoftco Avatar answered Sep 18 '22 07:09

vsoftco


Your std::list<Foo> contains actual Foo objects. When you call list.push_back(foo), it makes a copy of foo. You are then declaring foo2 to reference that copied object, not the original object. That is why foo is not updated when the members of foo2 are changed.

Try using pointers instead:

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;
}
like image 22
Remy Lebeau Avatar answered Sep 17 '22 07:09

Remy Lebeau