mpen
mpen

Reputation: 283355

How to extend lifetime of member reference?

Example code:

#include <iostream>
using namespace std;

struct Data {
    int d;
};

class Foo {
    public:
    explicit Foo(const Data& data) : data_(data) {}
    
    void PrintData() {
      std::cout << data_.d << std::endl;
    }
    
    private:
    const Data& data_;
};

Foo CreateFoo(int i) {
    Data d;
    d.d = i;
    return Foo(d);
}

int main() {
    Foo foo = CreateFoo(5);
    foo.PrintData();  // prints 0 instead of 5
    return 0;
}

https://ideone.com/KpCstl

foo.data_ is a reference to the Data created in CreateFoo which goes out of scope at the end of the function.

Without changing the definition of class Foo (e.g. to remove the reference), how can I extend the lifetime of d/data_, preferably so that it matches the lifetime of the Foo instance?

Is there perhaps some container object that I can wrap Foo in that will keep all its deps alive and return that from CreateFoo instead?


In the actual usage, our framework will keep Data alive, so it will outlive Foo as intended. But for my test code, I need to find another mechanism to keep Data alive at least until the test run is complete. I can use some kind of singleton to keep it alive, but I'm looking for a cleaner solution.

Upvotes: 1

Views: 438

Answers (3)

cigien
cigien

Reputation: 60460

This solution should work for you:

Foo CreateFoo(int i) {
    static std::forward_list<Data> v;
    v.push_front(Data{i});
    return Foo(v.front());
}

Maintain a static container of Data objects, add a new object on every call to CreateFoo, and hand out references to the last inserted object.

Since the container is static you won't have lifetime issues. Also, a container of objects ensures that every call returns a Foo that binds to a unique Data object.

Also, note that you need a container that doesn't invalidate references to it on modification, so I chose std::forward_list.

Here's a demo.

Upvotes: 2

Abhinav Singh
Abhinav Singh

Reputation: 312

If you want to keep your element alive even after scopes ends then you should use pointer, but again raw pointer is dangerous, so to counter this use std::shared_ptr, but then you wont be able to use ref variable to shared_ptr but an actual shared_ptr variable to keep the object of Data alive.

struct Data{
  int data;
};

class Foo{
public:
  explicit Foo(std:shared_ptr<Data>) {data = _data;}
  Foo createFoo(){
     shared_ptr<Data> d(new Data);
     return Foo(d);//this thing makes sure your shared pointer gets copied and it does not go out of scope
 }
 std::shared_ptr<Data> data;
};

Upvotes: 0

Anonymous1847
Anonymous1847

Reputation: 2598

If you want to keep the reference in the member variable, then likely you will need to use dynamic allocation. Think about it -- the reference must point to some Data object somewhere that has the same lifetime as the Foo, but the Data object can't be stored with the Foo object, as the Foo object has no space for it, only for a reference. So generally in this case it is allocated on the heap. I would suggest using a unique_ptr to automatically manage this Data object:

#include <iostream>
#include <memory>
using namespace std;

struct Data {
    int d;
};

class Foo {
    public:
    explicit Foo(const Data& data) : data_(std::make_unique<Data>(data)) {}
    
    void PrintData() {
      std::cout << data_->d << std::endl;
    }
    
    private:
    std::unique_ptr<Data> data_;
};

Foo CreateFoo(int i) {
    Data d;
    d.d = i;
    return Foo(d);
}

int main() {
    Foo foo = CreateFoo(5);
    foo.PrintData();
    return 0;
}

Note that a unique_ptr allows only for one object to have ownership, which will disallow Foos from being copied unless you override the relevant methods. If you want copied Foos to have references to the same object, use shared_ptr and make_shared instead. If you want copied Foos to copy their Data objects, overload the copy constructor and operator=, or better yet, turn the Data pointer into a plain Data member.

If you don't want to have Foos own and manage their Datas, then I would suggest:

#include <iostream>
#include <memory>
using namespace std;

struct Data {
    int d;
};

class Foo {
    public:
    explicit Foo(Data& data) : data_(data) {}


    void PrintData() {
      std::cout << data_->d << std::endl;
    }

    private:
    Data& data_;
};

struct FooWithData {
    Data foo_data;
    Foo foo;

    FooWithData(const FooWithData& other)
      : foo_data(other.foo_data),
        foo(foo_data) {}
    
    FooWithData(FooWithData&& other)
      : foo_data(other.foo_data),
        foo(foo_data) {}

    FooWithData& operator=(FooWithData& other) {
        foo_data = other.foo_data;
    }
};

FooWithData CreateFoo(int i) {
    FooWithData ret;
    ret.foo_data = Data { i };
    ret.foo = Foo(ret.foo_data);
    return ret;
}

int main() {
    FooWithData foo_compound = CreateFoo(5);
    foo_compound.foo.PrintData();
    return 0;
}

Upvotes: 3

Related Questions