This isn't so much about scope but about reason ;)
Ok, where you comment "works only by chance"....that's not actually right. It works, but not well.
A new person is allocated in getPersonTwo, and the return by de-reference (what's stored at *pp) returns the reference to the object.
All good. Just, not good.
Person p2 gets a reference from getPersonTwo to that instance created in the function, which is still there and all is fine except....
...you just can't easily delete it.
You can, but you have no indication that you must.
Since you know what happened, you could contrive a solution with "delete (&p2)", which is the same as "delete *pp" - if you had pp, but you don't.
Otherwise, without any call to delete, p2 has an object that isn't going to evaporate, and isn't working by accident or chance. This will always work in the sense that p2 is valid, there's not going to be some strange stack oriented corruption, and no other strange behavior other than a memory leak.
A memory leak is a bad thing, and one could argue that a memory leak is a failure of good code, but p2.name isn't working by chance, it's working because all that is required is there, still valid, not leaving, unambiguously fine.
getPersonFour also works by the same reasoning, except there's the same memory leak. This time, however, p4 is copying from the reference to p3. p4 is a copy, and the copy comes from a valid object. The same issue with the memory leak still impacts the "new Person" from getPersonFour, but that doesn't corrupt anything about what happens when p3 returns the reference or p4 gets a copy of that instance.
| OR since p3 is in scope in main this means the object created in this member function won't go out of scope |
Actually, no. There is no relationship between the scope of p3 and the reference returned from getPersonFour, because that function does not refer to any member of the p3 instance in that context. It is a candidate for being a static function because it uses no Person members. With no such usage, the scope of p3 is irrelevant.
EDIT: I wrote this incorrectly the first time, as @coder777 pointed out
This is entirely different than the seemingly similar problem from:
Person *& getPersonPointer()
Person *p = new Person("eve", 99 );
Person *& p5 = getPersonPointer();
In this situation, p is formed on the stack. The instance of Person "eve" is not, it's allocated from RAM. However, the pointer p is on the stack, and it evaporates when the function returns.
Unfortunately, what it returns to p5 is that pointer's reference on the stack. THAT is an issue of scope. "p", in getPersonPointer, has lost scope when the function returns. It's storage is gone. The compiler should warn.
Under the hood, what getPersonPointer does is return a reference to a pointer, which is implemented by the compiler as the address of that pointer, but the type gives us "special" usage cases.
p5 gets a copy of that pointer p through p's address, and THAT works by shear chance. At some point, soon, when another function is called, the stack could be completely changed and that pointer is gone.
Still, it's a hideous little bug because p5 is a copy of that pointer's address. It continues to be the valid content of the new'd Person "eve", so even though the corruption of the stack may happen, it won't impact the copy in p5, but that's because it happens before anything can corrupt the stack and thus corrupt the address of p.
It seems, by testing, to be valid code....except....optimization.
If you build and test this stuff in debug mode, with optimizations turned off, behavior will be absolutely "classic" - there will be stack frames, there will be function calls...
If you build with optimizations turned on, it is possible the compiler doesn't even generate a function call, there is no stack, and the result could go either way...it could seem to be even more stable and work, or it could go wildly wrong and fail with any alteration to the code in the future.