class scope

Hi guys,

in terms of scope , getPersonOne is valid, getPersonTwo is invalid or can cause undefined behaviour, but getPersonThree in Person this is tricky, so lets say we create an object p4 and assign it using the object p3's member function getPersonThree, this function will return a new Person and set it equal to p4 but won't this function go out of scope so won't this be undefined behaviour... OR since p3 is in scope in main this means the object created in this member function won't go out of scope until the object that created is destroyed?

thanks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

using namespace std;

class Person{

  public:

      string name;
      int age;

      Person();
      Person(string name,int age): name(name),age(age){}

      Person& getPersonFour(){

          Person* pd = new Person("ps",22);
          return *pd;
      }

};

Person getPersonOne(){


   return Person("add",11);
}

Person& getPersonTwo(){

  Person* pp = new Person("nk",99);

  return *pp;
}


int main()
{
    Person p1 = getPersonOne();
    Person p2 = getPersonTwo();

    cout << p2.name << endl; // works but only by chance

    Person p3("adam",99);
    Person p4 = p3.getPersonFour();
    cout << p4.name << endl; // again works but by chance??

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

1
2
3
4
5
6
7
8
9
10
Person *& getPersonPointer()
{
 Person *p = new Person("eve", 99 );
 return p;
}

//where

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.




Last edited on
@Niccolo
This is entirely different than the seemingly similar problem from:
getPersonPointer() is perfectly valid. There is just no bug.

@adam2016
Everything is fine with your functions except for the memory leak that getPersonTwo() generates when copied like on line 40. So no 'works by chance'.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Person * getVia()
{
 Person *p = new Person("eve", 99 );
 return p;
}

Person * get()
{
 return new Person("adam", 99 );
}

//where

Person *p1 = getVia();
Person *p2 = get();
Person *p3 = new Person("lilith", 99 );

p3 points to lilith.
p2 points to adam.
p1 points to eve.

All fine, if someone remembers to deallocate.

1
2
3
4
5
6
7
8
9
Person * getStack()
{
 Person p("jack", 99 );
 return &p;
}

// where

Person *p4 = getStack();

The p4 is invalid. It has the address of an object that was on the stack.


The getPersonOne() and Person::getPersonFour() are practically identical.

1
2
3
4
5
6
7
Person& getRef()
{
  return *( new Person("jill",99) );
}

// where
Person p5 = getRef();

The function returns a reference to object that is in Free store.
(We usually access those objects via pointer, but reference is valid too.)

The caller does not store the reference.
Instead, it copy constructs p5 from the value of jill. Valid copy.

The jill does still exist, but nobody has a pointer (or reference) to it.
Nobody (except OS) can deallocate jill in orderly fashion. That is a memory leak.
yeah that was stupid of me I should have realised that all I was doing was allocating memory on the heap, so even when the functions went out of scope the objects would still exist until I freed them, I don't know how I didn't catch that, what I really meant to ask is this..

p5 is set to equal a person which is created with the p3 object, so here is the question will p5 or p in the function getPersonFive() go out of scope when the function ends? or will it stay in scope until the object p5 is destroyed?

my guess since my program actually crashes is the former( person p or p5 goes out of scope when the function ends )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61


#include <iostream>

using namespace std;

class Person{

  public:

      string name;
      int age;

      Person();
      Person(string name,int age): name(name),age(age){}

      Person& getPersonFour(){

          Person* pd = new Person("ps",22);
          return *pd;
      }

      Person& getPersonFive(){

         Person p("p",33);
         return p;

      }

};

Person getPersonOne(){


   return Person("add",11);
}

Person& getPersonTwo(){

  Person* pp = new Person("nk",99);

  return *pp;
}


int main()
{
    Person p1 = getPersonOne();
    Person p2 = getPersonTwo();

    cout << p2.name << endl;

    Person p3("adam",99);
    Person p4 = p3.getPersonFour();
    cout << p4.name << endl;

    Person p5 = p3.getPersonFive();

}




The p4 is invalid. It has the address of an object that was on the stack.


that may answer my question but as said before even though an object is in memory that does not matter right? once the function goes out of scope weather or not the object exists the Person object created in the functions activation record or stack frame will be invalid?
Last edited on
There is a game of "be verbose":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>

using std::string;

struct Person {
  string name;
  int age;

  Person( string name, int age )
  : name(name),age(age)
  { std::cout << "ctor " << name << '\n'; }

  Person( const Person& rhs )
  : name(rhs.name), age(rhs.age)
  { std::cout << "copy ctor " << name << '\n'; }

  Person( Person&& rhs )
  : name(std::move(rhs.name)), age(std::move(rhs.age))
  { std::cout << "move ctor " << name << '\n'; }

  Person& operator= ( const Person& rhs )
  {
    name = rhs.name;
    age = rhs.age;
    std::cout << "copy assign " << name << '\n';
    return *this;
  }

  ~Person()
  { std::cout << "dtor " << name << '\n'; }
};

Person& get()
{
  Person p1( "Wick", 33 ); // warning: reference to local variable 'p' returned [-Wreturn-local-addr]
  return p1;
}

int main()
{
  Person p5 = get();
  std::cout << "Hello " << p5.name << '\n';
}

ctor Wick
dtor Wick
copy ctor 
Hello 
dtor 

The destructor of function-local variable 'p1' is called before the copy constructor of 'p5'. Hence the result is undefined behaviour.

Without the reference:
1
2
3
4
5
6
7
8
9
10
11
12
Person get()
{
  Person p1( "Wick", 33 );
  std::cout << "Hi " << p1.name << '\n';
  return p1;
}

int main()
{
  Person p5 = get();
  std::cout << "Hello " << p5.name << '\n';
}

ctor Wick
Hi Wick
Hello Wick
dtor Wick

Notice the return value optimization; p1 somehow became p5 without copy or move.
As @coder777 pointed out, I made a mistake in my code example - I got distracted writing the post and lost track a bit...it's been edited.
Registered users can post here. Sign in or register to post.