While working on a project, I came across an interesting problem when passing an object into another object through its constructor when the object passed in is guaranteed to outlive (in terms of memory lifetime) the recipient object. Please bare in mind that I am still learning the ins-and-outs of C++11/C++14, so I am looking for constructive discussion that will help in my understanding of memory management and lifetimes with C++11/C++14-style semantics.
The setup for this question is as follows:
class TopLevelClass {
public:
void someMethod (int someValue) {
// Do some work
}
std::unique_ptr<Context> getContext () {
return std::make_unique<Context>(this);
}
};
class Context {
public:
Context (TopLevelClass* tlc) : _tlc(tlc) {}
void call (int value) {
// Perform some work and then call the top level class...
_tlc->someMethod(value);
}
protected:
TopLevelClass* _tlc;
};
Although a valid alternative to this setup would be to pass the TopLevelClass
as an argument into the call
method of the Context
class, this is not possible in the scenario I am illustrating: The client code with access to a Context
object may not have access to the TopLevelClass
object.
While the code illustrated above is functionality correct for my needs, I feel as though there exists a code smell. Namely, storing a handle to the TopLevelClass
object as a raw pointer does not convey the fact that the Context
class is not responsible for managing the lifetime of this pointer (since, in this case, the TopLevelClass
is guaranteed to outlive any Context
object). Additionally, with use of C++11, I am hesitant to use a raw pointer, rather than a smart pointer (as per Scott Meyer's suggestion in Effective Modern C++).
One alternative I explored is to pass in the handle to the TopLevelClass
using a shared pointer and storing this handle within the Context
class as a shared pointer. This requires that the TopLevelClass
inherit from std::enabled_shared_from_this
in the following manner:
class TopLevelClass : public std::enable_shared_from_this<TopLevelClass> {
public:
// Same "someMethod(int)" as before...
std::unique_ptr<Context> getContext () {
return std::make_unique<Context>(shared_from_this());
}
};
class Context {
public:
Context (std::shared_ptr<TopLevelClass> tlc) : _tlc(tlc) {}
// Same "call(int)" as before...
protected:
std::shared_ptr<TopLevelClass> _tlc;
};
The downside to this approach is that unless a std::shared_ptr
exists for TopLevelClass
a priori, then a std::bad_weak_ptr
exception will be thrown (for more information, see this post). Since, in my case, there is no std::shared_ptr<TopLevelClass>
created in the code, I cannot use the std::enable_shared_from_this<T>
approach: I am restricted to returning a single instance of TopLevelClass
using a static
raw pointer, as per the requirements of my project, as follows
static TopLevelClass* getTopLevelClass () {
return new TopLevelClass();
}
Is there an approach that exists that conveys the fact that Context
is not responsible for managing its handle to the TopLevelClass
instance, since the TopLevelClass
will be guaranteed to outlive any Context
object? I am also open to suggestions about changing the design that will side-skirt the problem altogether, so long as the design change does not overly complicate the simplicity of the design above (i.e., creating many different classes in order to get around simply passing a single pointer into the constructor of Context
).
Thank you for your help.