Как управлять элементами std:: list в качестве ссылок?
У меня есть следующий код:
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;
}
Я хочу, чтобы оба foo
и foo2
ссылались на один и тот же объект. Поэтому, когда я назначаю 5
в foo2.var2
, я хотел бы также изменить foo.var2
. Однако, как видно из следующего вывода, этого не происходит:
foo (0x7fffffffe140): [Foo] 1,2
foo2 (foo from list) (0x61ac30): [Foo] 1,5
Каким будет правильный способ?
Ответы
Ответ 1
Когда вы используете 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
s:
http://coliru.stacked-crooked.com/a/fb1fd67996d6e5e9
Ответ 2
Ваш 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;
}
Ответ 3
Если вы измените свой вывод на это:
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
. Когда вы вставляете что-то в список, он помещает копию в этот список, а не ссылку.
Ответ 4
Для этого вам понадобится эталонная семантика вместо семантики значений. Самый простой способ реализовать ссылочную семантику - через указатели. Только некоторые небольшие изменения в вашем примере сделают это:
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;
}