Home > front end >  Preserve References in C During Functioncalls
Preserve References in C During Functioncalls

Time:11-27

I want to preserve references during function calls in C . In The following example the function baz creates two classes: Foo and Bar. The class foo is then passed to Bar as reference but instead of keeping foo until Bar is destroyed, it is destroyed after function execution.

In all other cases the foo references stays until the referencing Bar class is destroyed.

My question now: is there a way of preserving references inside of classes until they are destroyed without new/delete or do I have to go the buffer-overflow way ?

Also what is the sense of references when they create such faulty behavior, because I can't safely access Foo references in Bar like this.

What I want is either pass a default (null, nofoo) class to Bar or a customized one without keeping track of new/delete because I am planning of passing a whole bunch of different classes without the memory overhead derivation would cause. Similar to a container based behavior.

class Foo
{
private:
  int size;
  int state;
  int property;
  float value;

public:
  Foo ( int s )
  {
    size = s;
    state = 0;
    property = 0;
    value = 0.0;
    print ( "class Foo constructed with size: %i\n", size );
  }

  ~Foo ( )
  {
    print ( "class Foo destructed  with size: %i\n", size );
  }
};

Foo nofoo ( 0 );

class Bar
{
private:
  Foo &fooref;

public:
  Bar ( Foo &ref = nofoo ) : fooref ( ref )
  {
    print ( "class Bar constructed with foo: %i, nofoo: %i\n", (Foo *)&fooref, (void *)&ref==(void *)&nofoo );
  }

  ~Bar ( )
  {
    print ( "class Bar destructed with foo: %i\n", (Foo *)&fooref );
  }
};

Foo faz()
{
  print ( "function faz called\n" );
  
  Foo ret ( 2 );
  return ret;   // Foo is not destroyed here
}

Bar baz()
{
  print ( "function baz called\n" );
  
  Foo foo4 ( 4 );
  Bar ret ( foo4 );
  return ret;   // Foo is destroyed here but stays as reference in Bar
}


int main ()
{
    print ( "test code started\n" );

    Foo foo ( 1 );
    Bar bar ( foo );
    
    print ( "size of foo: %i\n", sizeof(foo) );
    print ( "size of bar: %i\n", sizeof(bar) );

    Foo fooref = faz();
    print ( "size of fooref=faz(): %i\n", sizeof(fooref) );
    
    Bar bar2;
    print ( "size of bar2: %i\n", sizeof(bar2) );

    Bar barref = baz();
    print ( "size of barref=baz(): %i\n", sizeof(barref) ); 

    return 0;
}

Output of this code:

test code started
class Foo constructed with size: 1
class Bar constructed with foo: -96188384, nofoo: 0
size of foo: 16
size of bar: 8
function faz called
class Foo constructed with size: 2
size of fooref=faz(): 16
class Bar constructed with foo: 1977483344, nofoo: 1
size of bar2: 8
function baz called
class Foo constructed with size: 4
class Bar constructed with foo: -96188480, nofoo: 0
class Foo destructed  with size: 4
size of barref=baz(): 8
class Bar destructed with foo: -96188480
class Bar destructed with foo: 1977483344
class Foo destructed  with size: 2
class Bar destructed with foo: -96188384
class Foo destructed  with size: 1
class Foo destructed  with size: 0

One could use pointers instead of references, but references are (at least should be) safer. Whenever I would call a member of a Foo pointer I have to check if it is valid.

A way could be unique_ptr<> template type which, at least, keeps track of deletion. But still I would have to check for nullptr. What I require is some kind of unique_ptr for references.

CodePudding user response:

There is nothing that will allow you to extend the lifetime of a reference like you want. A reference does not give you that. Pointers are how we share and transfer ownership because it is only through a pointer that we can take on responsibility of deleting an object.

When we accept a reference as a parameter, we are communicating that we are not accepting any responsibility for the lifetime of the parameter.

With pointers, we have three options and different semantics for each:

  1. Raw pointers. Here, it is unspecified who is responsible for the object. If you follow the general guidance, raw pointers should only be used when we are indicating that we are not taking on any responsibility for the lifetime of the object. Rather than relying on convention we can use one of the other options.
  2. Unique pointers. With unique_ptr parameters we indicate that we are taking ownership of an object from elsewhere. Ownership is transferred to the function and nothing outside of the function owns it.
  3. Shared pointer. Here we are indicating that we are taking on shared responsibility. If we are the last ones to hold the shared_ptr, we will delete it. If we are not the last one, we will not delete it.

All three of these pointer options give us tools for deleting the object. References are used to liberate us from any need to be responsible for deletion. In your case, you want a Bar to extend the lifetime of a Foo. This means you must address the deletion problem in some way. You do that with pointers.

Regarding having to check for validity - yes. Pointers give us the power of deletion. Because it can be deleted, it can also be invalid. References do not give us the power of deletion and thus they should always be valid. If you want the power of deletion (which you do in your example), you have to check for validity.

There is a huge caveat to all of this. You can always turn a reference into a pointer and vice-versa. A pointer and a reference are the same thing in the end, but we use them to communicate different things in our code and trying to break these conventions will generally not make your life easier.

I'll also add that your function baz is not indicating any problem with references. You created foo4 on the stack. Creating something on the stack also means it will be destroyed when the stack is popped. The fact that you are using a reference in Bar is irrelevant. If you used a pointer, you would have the same issue. If you want a Foo to last and have a reference or pointer remain valid, it must be allocated somewhere that remains valid for the duration of its usage (heap, higher level stack frame, global memory, etc.).

CodePudding user response:

In your example, you're trying to extend lifetime of object existing in the scope of function (Foo foo4 ( 4 );). In C you can't do it this way, you need creating objects out of function's scope, in dynamic or global memory, and point to them. So please consider use pointers (all types of them), to achieve what you'd like.

Alternatively, if you pass ownership of a Foo object to a Bar object, it will survive function finishing. But it wouldn't be a reference in this case. You need to either move or copy an object.

You can pass references if you're ready to control lifetime of objects (organize the code the way allowing you clearly understand when object is created and when it will be destroyed). In this sense, references are less safe than pointers in C . At least, smart pointers that allow you to avoid calling delete manually.

  • Related