Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend lifetime of the local variable or what is right way to use references

I was developing some class and bumped for this question. Consider I have following class:

struct A
{
    int *p;
    A() 
    {
        p = new int(1); 
        cout << "ctor A" << endl; 
    }
    A(const A& o) 
    { 
        cout << "copy A" << endl;
        p = new int(*(o.p));
    }
    A(A&& o) 
    {
        cout << "move A" << endl;
        p = std::move(o.p);
        o.p = NULL;
    }
    A& operator=(const A& other)
    {       
        if (p != NULL)
        {
            delete p;
        }
        p = new int(*other.p);
        cout << "copy= A" << endl;
        return *this;
    }
    A& operator=(A&& other)
    {       
        p = std::move(other.p);
        other.p = NULL;
        cout << "move= A" << endl;
        return *this;
    }
    ~A()
    {
        if(p!=NULL)
            delete p;
        p = NULL;
        cout << "dtor A" << endl;
    }
};

And following class which has A as a property:

class B {
public:
  B(){}
  A myList;
  const A& getList() { return myList; };
};

And this function which checks for some variable value and returns different objects in different cases:

B temp;
A foo(bool f)
{
    A a;
    *a.p = 125; 
    if (f)
        return a;
    else
    {
        return temp.getList();
    }
}

Now, I am want to use this function like this:

A list1 = foo(true);
if(list1.p != NULL)
    cout << (*list1.p) << endl;

cout << "------"<<endl;
A list2 = foo(false);
if (list2.p != NULL)
    cout << (*list2.p) << endl;

The purpose of this situation is:

Function foo should return (or move) without copying some local object with changes in p if argument is true, or should return property of global variable temp without calling copy constructors of A (i.e. return reference of myList) and also it should not grab myList from B (it should not destroy myList from B, so std::move can not be used) if argument is false.

My question is:

How should i change function foo to follow upper conditions? Current implementation of foo works right in true case and moving that local variable, but in case false it calls copy constructor for list2. Other idea was to somehow extend lifetime of local variable, but adding const reference did not work for me. Current output is:

ctor A
ctor A
move A
dtor A
125
------
ctor A
copy A
dtor A
1
dtor A
dtor A
dtor A
like image 216
Egor Schavelev Avatar asked Jul 21 '17 08:07

Egor Schavelev


People also ask

How can you extend the lifetime of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

What is the lifetime of a methods local variable?

In the case of a local variable, the lifetime begins at the declaration (and therefore at the beginning of the scope) and ends at the end of the method in which the variable was declared (the end of the scope).

What is the lifetime of a variable example?

These types of variables can be declared using the static keyword, global variables also have a static lifetime: they survive as long as the program runs. Example : static int count = 0; The count variable will stay in the memory until the execution of the program finishes.

What is the lifetime of a variable?

The lifetime of a variable or object is the time period in which the variable/object has valid memory. Lifetime is also called "allocation method" or "storage duration."


2 Answers

If you can change B to

class B {
public:
  B(){}
  std::shared_ptr<A> myList = std::make_shared<A>();
  const std::shared_ptr<A>& getList() const { return myList; };
};

then foo can be:

B b;
std::shared_ptr<A> foo(bool cond)
{
    if (cond) {
        auto a = std::make_shared<A>();
        *a->p = 125; 

        return a;
    } else {
        return b.getList();
    }
}

Demo

Output is

ctor A
ctor A
125
------
1
dtor A
dtor A
like image 177
Jarod42 Avatar answered Sep 19 '22 17:09

Jarod42


The simplest solution is probably to use std::shared_ptr as in Jarod42's answer. But if you want to avoid smart pointers, or if you can't change B you can probably create your own wrapper class that might or might not own an A. std::optional might be quite convenient for this:

class AHolder {
  private:
    std::optional<A> aValue;
    const A& aRef;
  public:
    AHolder(const A& a) : aRef(a) {}
    AHolder(A&& a) : aValue(std::move(a)), aRef(aValue.value()) {}
    const A* operator->() const { return &aRef; }
};

The class contains an optional to own the A if required and you can use move-semantics to move it in. The class also contains a reference (or pointer) that either references the contained value or references another object.

You can return this from foo:

AHolder foo(bool f)
{
    A a;
    *a.p = 125; 
    if (f)
        return a;
    else
    {
        return temp.getList();
    }
}

And the caller can access the contained reference:

  auto list1 = foo(true);
  if(list1->p != nullptr)
    cout << (*list1->p) << endl;

  cout << "------"<<endl;
  auto list2 = foo(false);
  if (list2->p != nullptr)
    cout << *list2->p << endl;

Live demo.

If you don't have access to std::optional there is boost::optional or you could use std::unique_ptr at the cost of a dynamic memory allocation.

like image 22
Chris Drew Avatar answered Sep 22 '22 17:09

Chris Drew