-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(NLVObject): use smart pointers for tracking object lifetime #76
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,37 +6,15 @@ | |
|
||
#include <memory> | ||
#include <nan.h> | ||
#include <memory> | ||
#include <type_traits> | ||
#include <forward_list> | ||
|
||
using namespace node; | ||
using namespace v8; | ||
|
||
class NLVObjectBase; | ||
|
||
// hold a reference to a "child" object in a way that can be safely invalidated | ||
// if the child is destroyed by the GC before the parent. | ||
class NLVObjectBasePtr | ||
{ | ||
public: | ||
NLVObjectBasePtr(NLVObjectBase *ref) : ref_(ref), valid_(true) {} | ||
bool IsValid() const { return valid_; } | ||
NLVObjectBase* GetPointer() const { | ||
if (!valid_) { | ||
//Nan::ThrowReferenceError("attempt to access invalid NLVObjectBase pointer"); | ||
return NULL; | ||
} | ||
|
||
return ref_; | ||
} | ||
|
||
void SetInvalid() { | ||
ref_ = NULL; | ||
valid_ = false; | ||
} | ||
|
||
protected: | ||
NLVObjectBase *ref_; | ||
bool valid_; | ||
}; | ||
|
||
#define NLV_STRINGIFY0(v) #v | ||
#define NLV_STRINGIFY(v) NLV_STRINGIFY0(v) | ||
|
||
|
@@ -49,38 +27,58 @@ class NLVObjectBasePtr | |
friend class NLVObject; | ||
|
||
|
||
class NLVObjectBase : public Nan::ObjectWrap | ||
class NLVObjectBase : public Nan::ObjectWrap, public std::enable_shared_from_this<NLVObjectBase> | ||
{ | ||
public: | ||
void AddToParent(NLVObjectBase* parent) { | ||
parent->PushChild(shared_from_this()); | ||
} | ||
|
||
void ClearChildren() { | ||
for (auto& ptr: children_) { | ||
if (auto object = ptr.lock()) { | ||
object->ClearHandle(); | ||
object->ClearChildren(); | ||
} | ||
} | ||
children_.clear(); | ||
} | ||
|
||
virtual void ClearHandle() = 0; | ||
virtual void ClearChildren() = 0; | ||
virtual void SetParentReference(NLVObjectBasePtr *parentReference) = 0; | ||
|
||
std::vector<NLVObjectBasePtr*> children_; | ||
void PushChild(const std::shared_ptr<NLVObjectBase>& child) { | ||
children_.emplace_front(child); | ||
} | ||
std::forward_list<std::weak_ptr<NLVObjectBase>> children_; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see the case for using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you misunderstood the use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant "random insertion" I guess, from the docs: "std::forward_list is a container that supports fast insertion and removal of elements from anywhere in the container." That's its main point in life, and because of the linked nature of it has a non-contiguous memory layout. std::vector on the other hand in terms of memory layout is contiguous and doesn't require an additional pointer-per-item overhead for linking. So to recap: std::vector is more cache-friendly, involves less allocations and doesn't have the memory overhead. At the end of the day we're really splitting hairs because this is an incredibly small dataset (if any), but I guess I just didn't see the need for a special case container here. |
||
}; | ||
|
||
template <typename ParentClass, typename HandleType, typename CleanupHandler> | ||
class NLVObject : public NLVObjectBase | ||
{ | ||
std::shared_ptr<NLVObjectBase> selfPtr; | ||
public: | ||
typedef HandleType handle_type; | ||
|
||
typedef typename std::remove_pointer<HandleType>::type HandleValue; | ||
|
||
NLVObject(HandleType handle) : handle_(handle, CleanupHandler::cleanup), parentReference_(NULL) {} | ||
|
||
NLVObject(HandleType handle) : handle_(handle, CleanupHandler::cleanup) { | ||
} | ||
|
||
~NLVObject() { | ||
// calling virtual ClearHandle() will break if overridden by subclasses | ||
ClearHandle(); | ||
if (parentReference_ != NULL) { | ||
parentReference_->SetInvalid(); | ||
} | ||
} | ||
|
||
void RegisterSelf() { | ||
selfPtr = shared_from_this(); | ||
} | ||
|
||
static v8::Local<v8::Object> NewInstance(handle_type handle) { | ||
Nan::EscapableHandleScope scope; | ||
Local<Function> ctor = Nan::New<Function>(ParentClass::constructor); | ||
Local<Object> object = Nan::NewInstance(ctor).ToLocalChecked(); | ||
ParentClass *class_instance = new ParentClass(handle); | ||
auto shared = std::shared_ptr<ParentClass>(new ParentClass(handle), [=](ParentClass*) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True enough but
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, hrm I didn't see you actually using the callback. I guess we can discuss that when we're focusing on these particular changes once you split the commits. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also I think we should both try to brush up on Nan::PersistentBase, in particular its In particular (from the embedder's guide): "Use a persistent handle when you need to keep a reference to an object for more than one function call, or when handle lifetimes do not correspond to C++ scopes." |
||
// here we can now if GC has destroyed our object | ||
}); | ||
ParentClass *class_instance = shared.get(); | ||
class_instance->RegisterSelf(); | ||
class_instance->Wrap(object); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think in this case we shouldn't wrap this guy in a shared pointer. The idea of the smart pointer is to manage the lifetime of that object, assuming we have control over its lifetime. In this particular case we are about to give up ownership to v8 so it's a bit redundant here imho There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this needs justification. To use |
||
return scope.Escape(object); | ||
} | ||
|
@@ -92,8 +90,7 @@ class NLVObject : public NLVObjectBase | |
|
||
const HandleType virHandle() const { | ||
return handle_.get(); | ||
} | ||
|
||
} | ||
|
||
NAN_INLINE static ParentClass* Unwrap(v8::Local<v8::Object> val) { | ||
if(!ParentClass::IsInstanceOf(val)) { | ||
|
@@ -126,34 +123,12 @@ class NLVObject : public NLVObjectBase | |
return Unwrap(val)->virHandle(); | ||
} | ||
|
||
virtual void ClearHandle() { | ||
void ClearHandle() { | ||
handle_.reset(); | ||
} | ||
|
||
virtual void ClearChildren() { | ||
std::vector<NLVObjectBasePtr*>::const_iterator it; | ||
for (it = children_.begin(); it != children_.end(); ++it) { | ||
NLVObjectBasePtr *ptr = *it; | ||
if (ptr->IsValid()) { | ||
NLVObjectBase *obj = ptr->GetPointer(); | ||
obj->ClearChildren(); | ||
obj->ClearHandle(); | ||
obj->SetParentReference(NULL); | ||
delete ptr; | ||
} | ||
} | ||
|
||
children_.clear(); | ||
} | ||
|
||
virtual void SetParentReference(NLVObjectBasePtr *parentReference) { | ||
parentReference_ = parentReference; | ||
} | ||
|
||
protected: | ||
std::unique_ptr<HandleValue, decltype(&CleanupHandler::cleanup)> handle_; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||
NLVObjectBasePtr* parentReference_; | ||
|
||
}; | ||
|
||
namespace NLV { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mbroadst this is the reward, ability to see if those weak pointers are stil valid.