Can someone tell me if this is safe, because I think it isn't:
class A
{
public:
A(int*& i) : m_i(i)
{}
int*& m_i;
};
class B
{
public:
B(int* const& i) : m_i(i)
{}
int* const & m_i;
};
int main()
{
int i = 1;
int *j = &i;
A a1(j); // this works (1)
A a2(&i); // compiler error (2)
B b(&i); // this works again (3)
}
I understand why (1) works. We are passing a pointer, the function accepts it as a reference.
But why doesn't (2) work? From my perspective, we are passing the same pointer, just without assigning it to a pointer variable first. My guess is that &i is an rvalue and has no memory of its own, so the reference cannot be valid. I can accept that explanation (if it's true).
But why the heck does (3) compile? Wouldn't that mean that we allow the invalid reference so b.m_i is essentially undefined?
Am I completely wrong in how this works? I am asking because I am getting weird unit test fails that I can only explain by pointers becoming invalid. They only happen for some compilers, so I was assuming this must be something outside the standard.
So my core question basically is: Is using int* const & in a function argument inherently dangerous and should be avoided, since an unsuspecting caller might always call it with &i like with a regular pointer argument?
Addendum: As #franji1 pointed out, the following is an interesting thought to understand what happens here. I modified main() to change the inner pointer and then print the members m_i:
int main()
{
int i = 1;
int *j = &i; // j points to 1
A a1(j);
B b(&i);
int re = 2;
j = &re; // j now points to 2
std::cout << *a1.m_i << "\n"; // output: 2
std::cout << *b.m_i << "\n"; // output: 1
}
So, clearly a1 works as intended.
However, since b cannot know that j has been modified, it seems to hold a reference to a "personal" pointer, but my worry is that it is not well defined in the standard, so there might be compilers for which this "personal" pointer is undefined. Can anyone confirm this?
A's constructor takes a non-const reference to an int* pointer. A a1(j); works, because j is an int* variable, so the reference is satisfied. And j outlives a1, so the A::m_i member is safe to use for the lifetime of a1.
A a2(&i); fails to compile, because although &i is an int*, operator& returns a temporary value, which cannot be bound to a non-const reference.
B b(&i); compiles, because B's constructor takes a reference to a const int*, which can be bound to a temporary. The temporary's lifetime will be extended by being bound to the constructor's i parameter, but will then expire once the constructor exits, thus the B::m_i member will be a dangling reference and not be safe to use at all after the constructor has exited.
j is an lvalue and as such it can be bound to a non-const lvaue reference.
&i is a prvalue and it cannot be bound to non-const lvalue reference. That's why (2) doesn't compile
&i is a prvalue (a temporary) and it can be bound to a const lvalue reference. Bounding a prvalue to a reference extends the lifetime of the temporary to the lifetime of the reference. In this case this temporary lifetime is extended to the lifetime of the constructor parameter i. You then initialize the reference m_i to i (constructor parameter) (which is a reference to the temporary) but because i is an lvalue the lifetime of the temporary is not extended. In the end you end up with a reference member m_i bound to an object which is not alive. You have a dangling reference. Accessing m_i from now on (after the constructor has finished) is Undefined Behavior.
Simple table of what can references bind to: C++11 rvalue reference vs const reference
Pointer is a memory address. For simplicity, think of a pointer as uint64_t variable holding a number representing the memory address of whatever. Reference is just a alias for some variable.
In example (1) you are passing a pointer to constructor expecting a reference to pointer. It works as intended, as compiler gets the address of memory where the value of pointer is stored and passes it to constructor. The constructor gets that number and creates an alias pointer. As a result you are getting an alias of j. If you modify j to point to something else then m_i will also be modified. You can modify m_i to point to something else too.
In example (2) you are passing a number value to the constructor expecting a reference to pointer. So, instead of an address of an address, constructor gets an address and compiler has no way to satisfy the signature of the constructor.
In example (3) you are passing a number value to constructor expecting a constant reference to pointer. Constant reference is a fixed number, just a memory address. In this case compiler understands the intent and provides the memory address to set in the constructor. As a result you are getting fixed alias of i.
EDIT (for clarity): Difference between (2) and (3) is that &i is not a valid reference to int*, but it is a valid const reference to int*.
Related
When creating a reference in C++ why does the compiler request a value and not an address.
For example:
int i;
int &j = i;
is valid.
int i;
int &j = *&i;
is valid.
int i;
int &j = &i;
is incorrect. If you are equating the address, why does it request a value?
A reference is an alias to an already-existing object or function. In particular case, this is an lvalue reference, so it is a reference to an lvalue, the address of an object is not an lvalue.
A reference to T can be initialized with an object of type T, a
function of type T, or an object implicitly convertible to T.
Considering this, the error we get, when try this says for itself:
invalid conversion from ‘int*’ to ‘int’ // from the &i to the &i
But for example, we can do this:
int i;
int *&&j = &i; // j is an rvalue reference to pointer to int
Also, as was noted in the comments, you should not confuse the & when it is used in a declaration, and when it is used as an operator (i.e. the address-of-operator).
You may benefit from looking at the documentation:
Reference Initialization and Reference declaration.
why does the compiler request a value
It doesn't request a value. You have the wrong frame of mind. All it needs is some expression that identifies an object. The variable name i is such an expression.
In some contexts, expressions that designate object cause an access to a stored value (for instance i = i + i; would incur an access). But not every use of i will cause its value to be read. So for instance when references are bound to it, i doesn't need to be (and indeed isn't) accessed for its value.
In the same vain, *&i doesn't always access the value of i. It's an expression, where the C++ standard says that * and & "collapse" here to simply designating i (with some minutia), but no access happens just due to using the operators themselves.
I know this is silly and the title probably isn't the answer..
I always thought of this as a pointer to the current object which is supplied in every method call from an object (which is not a static method)
but looking at what my code actually returns for example:
Test& Test::func ()
{
// Some processing
return *this;
}
the dereference of this is returned... and the return type is a reference to the object.... so what does that make this? Is there something under the hood I'm not understanding well?
Remember that a reference is simply a different name for an object.
That implies that returning a reference is the same thing as returning an object on type level of abstraction; it is not the same thing in the result: returning a reference means the caller gets a reference to the current object, whereas returning an object gives him (a reference to) a copy of the current object - with all the consequences, like the copy constructor being called, deep copy decisions are being made, etc.
From cppreference:
The keyword this is a prvalue expression whose value is the address of the object, on which the member function is being called.
And then (perhaps easier to grasp):
The type of this in a member function of class X is X* (pointer to X). If the member function is cv-qualified, the type of this is cv X* (pointer to identically cv-qualified X). Since constructors and destructors cannot be cv-qualified, the type of this in them is always X*, even when constructing or destroying a const object.
So, this is not a pointer to a reference, but just a pointer.
Actually you cannot have a pointer to a reference, because taking the address of a reference will give you the address of referenced object.
Further, there is no special syntax in C++ to form a reference. Instead references have to be bound on initialization, for example:
int x = 3;
int& y = x; // x is int, but y is int&
assert( &y == &x); // address of y is the address of x
Similar when returning a reference from a function:
int& get_x() {
static int x = 3;
return x;
}
To put it simply:
test t1; // t1 is a test object.
test& t2 = t1; // t2 is another name for t1.
test* t3; // t3 holds an address of a test object.
*t3; // derefernce t. which gives you, the test object that t3 points to.
this is a pointer to the current test object.
therefore *this is the current test object, and because the return value type is test&, when you call the function, you get the same object you called the function from.
#include <iostream>
using namespace std;
int main()
{
int i = 0;
cout << &i << endl;
const auto &ref = (short&&)i;
cout << &ref << endl;
return 0;
}
Why is &i different from &ref? (short&)i doesn't cause this problem. Does (short&&)i generate a temporary variable?
It's because you're doing a different type of cast. The C style explicit conversion cast does always a static cast, if it could be interpreted as a static cast; otherwise it does a reinterpret cast. And/or const cast as needed.
(short&&)i is a static cast because it can be interpreted as static_cast<short&&>(i). It creates a temporary short object, to which ref is bound. Being a different object, it has a different address.
(short&)i is a reinterpret cast because it cannot be interpreted as static_cast<short&>(i) which is ill formed. It reinterprets the int reference as short reference, and ref is bound to the the same object. Note that accessing the object through this reference would have undefined behaviour.
This creates a lvalue reference to a thing that exists:
const auto& ref = i;
The expressions &ref and &i will therefore give the same result.
This is also true of:
const auto& ref = (int&)i;
which is basically the same thing.
However, casting to something that is not a lvalue reference to T (so, to a value, or to an rvalue reference of another type!) must create a temporary; this temporary undergoes lifetime extension when bound to ref. But now ref does not "refer to" i, so the address-of results will differ.
It's actually a little more complicated than that, but you get the idea. Besides, don't write code like this! An int is not a short and you can't pretend that it is.
Apparently it creates a temporary.
Actually the compiler will tell you itself.
Try this:
auto &ref = (short&&)i;
cout << &ref << endl;
The error says:
error: non-const lvalue reference to type 'short' cannot bind to a
temporary of type 'short'
Test code here.
(short&&)i creates a temporary, so you take address of an other object, so address might differ.
Does dereferencing a pointer and passing that to a function which takes its argument by reference create a copy of the object?
In this case the value at the pointer is copied (though this is not necessarily the case as the optimiser may optimise it out).
int val = *pPtr;
In this case however no copy will take place:
int& rVal = *pPtr;
The reason no copy takes place is because a reference is not a machine code level construct. It is a higher level construct and thus is something the compiler uses internally rather than generating specific code for it.
The same, obviously, goes for function parameters.
In the simple case, no. There are more complicated cases, though:
void foo(float const& arg);
int * p = new int(7);
foo(*p);
Here, a temporary object is created, because the type of the dereferenced pointer (int) does not match the base type of the function parameter (float). A conversion sequence exists, and the converted temporary can be bound to arg since that's a const reference.
Hopefully it does not : it would if the called function takes its argument by value.
Furthermore, that's the expected behavior of a reference :
void inc(int &i) { ++i; }
int main()
{
int i = 0;
int *j = &i;
inc(*j);
std::cout << i << std::endl;
}
This code is expected to print 1 because inc takes its argument by reference. Had a copy been made upon inc call, the code would print 0.
No. A reference is more or less just like a pointer with different notation and the restriction that there is no null reference. But like a pointer it contains just the address of an object.
If a function returns an int, can it be assigned by an int value? I don't see it makes too much sense to assign a value to a function.
int f() {}
f() = 1;
I noticed that, if the function returns a reference to an int, it is ok. Is it restricted only to int? how about other types? or any other rules?
int& f() {}
f() = 1;
The first function returns an integer by-value, which is an r-value. You can't assign to an r-value in general. The second f() returns a reference to an integer, which is a l-value - so you can assign to it.
int a = 4, b = 5;
int& f() {return a;}
...
f() = 6;
// a is 6 now
Note: you don't assign a value to the function, you just assign to its return value.
Be careful with the following:
int& f() { int a = 4; return a; }
You're returning a reference to a temporary, which is no longer valid after the function returns. Accessing the reference invokes undefined behaviour.
What are Lvalues and Rvalues? by Danny Kalev
Lvalues and Rvalues by Dan Saks
Lvalues and Rvalues by Mikael Kilpeläinen
It's not limit to int only, but for the primitive types there no point in doing it. It's useful when you have your classes
If a function returns an object by value, then assignment is possible, even though the function call is an rvalue. For example:
std::string("hello") = "world";
This creates a temporary string object, mutates it, and then immediately destroys it. A more practical example is:
some_function(++list.begin());
You could not write list.begin() + 1, because addition is not possible on list iterators, but increment is fine. This example does not involve assignment, but assignment is just a special case of the more general rule "member functions can be called on rvalues".