struct configfs
{
int & foo;
configfs(int & foo_) : foo(foo_) {}
void set_foo(int & foo_)
{
foo = foo_;
}
};
int main() {
int a = 1;
configfs conf(a);
a = 3;
std::cout << conf.foo; // 3
std::cout << a; // 3
int b = 2;
conf.set_foo(b);
b = 9;
std::cout << conf.foo; // 2
std::cout << b; // 9
return 0;
}
When I initialize configfs
with int a
and then alter a in the main function it also alters configfs.foo
. But when I reassign configfs.foo
to a new reference and alter that reference in the main function, the behavior is different. Why? Is there a way so that when I run conf.set_foo(b)
and then alter b in the main scope, it also alters conf.foo
as well?
CodePudding user response:
Refercences can only be "assigned" during initialization. They cannot be "retargeted" the way like pointers. After the initialization the memory the reference refers to remains fixed and any assignment using =
results in the invokation of the assignment operator to the memory the reference refers to. That's the way it's specified in the C standard.
You could rewrite the example using a custom type with assignment operators printing the information about calls:
template<class T>
struct configfs
{
T& foo;
configfs(T& foo_) : foo(foo_) {}
void set_foo(T& foo_)
{
foo = foo_; // this uses the assignment operator assigning to the variable foo is an alias for
}
};
// Type wrapping the int and printing out the operations applied
struct TestWrapper
{
TestWrapper(int value)
: value(value)
{
std::cout << "TestWrapper::TestWrapper(" << value << ")\n";
}
TestWrapper(TestWrapper const& other)
: value(other.value)
{
std::cout << "TestWrapper::TestWrapper(TestWrapper{" << other.value << "})\n";
}
TestWrapper& operator=(TestWrapper const& other)
{
std::cout << "TestWrapper::operator=(TestWrapper{" << other.value << "})\n";
value = other.value;
return *this;
}
int value;
};
std::ostream& operator<<(std::ostream& s, TestWrapper const& val)
{
s << val.value;
return s;
}
int main() {
TestWrapper a = 1;
configfs<TestWrapper> conf(a);
a = 3;
std::cout << conf.foo << '\n'; // 3
std::cout << a << '\n'; // 3
TestWrapper b = 2;
conf.set_foo(b); // TestWrapper::operator=(TestWrapper{2})
b = 9;
std::cout << conf.foo << '\n'; // 2
std::cout << b << '\n'; // 9
return 0;
}
CodePudding user response:
When you wrote:
conf.set_foo(b);
The following things happen:
Member function
set_foo
is called on the objectconf
.Moreover, the reference parameter named
foo_
is bound the the argument namedb
. That is,b
is passed by reference.Next, the statement
foo = foo_;
is encountered. This is an assigment statement and not an initialization. What this does is that it assigns the value referred to by the parameterfoo_
to the object referred to by the data memberfoo
(which is nothing buta
here). You can confirm this by addingstd::cout<<a;
after the call toset_foo
as shown below:
int b = 2;
conf.set_foo(b);
std::cout<<a<<std::endl; //prints 2
This is because operations on a reference are actually operations on the object to which the reference is bound. This means that when we assign to a reference, we are assigning to the object to which the reference is bound. When we fetch the value of a reference, we are really fetching the value of the object to which the reference is bound.
Note:
Once initialized, a reference remains bound to its initial object. There is no way to rebind a reference to refer to a different
object.