Reputation: 283355
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;
}
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
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
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
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 Foo
s from being copied unless you override the relevant methods. If you want copied Foo
s to have references to the same object, use shared_ptr
and make_shared
instead. If you want copied Foo
s 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 Foo
s own and manage their Data
s, 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