0

I have some code that looks like this:

class Widget {
  public:
    static std::unique_ptr<Widget> make(OtherArgs args);  // factory pattern

    Widget(v8::isolate&& isolate, OtherArgs args);
  private:
    v8::Local<v8::Object> Widget::create_v8_object()

    v8::isolate isolate_;
    v8::Local<v8::Object> gadget_;
};

std::unique_ptr<Widget> Widget::make(OtherArgs args)
{
    v8::isolate isol;
    v8::HandleScope scope(isol.get());
    return std::make_unique<Widget>(std::move(isol), args);
}

Widget::Widget(v8::isolate&& isol, OtherArgs args) :
    isolate_(std::move(isol)),
    context_(isolate_.get()),
    gadget_(isolate_.get(), create_v8_object())
{
}

v8::Local<v8::Object> Widget::create_v8_object()
{
    v8::Local<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New(isolate_.get());
    // ...
    v8::Local<v8::Object> gadget = v8::Local<v8::Object>::New(isolate_.get(), tmpl->NewInstance());
    // ...
    return gadget;
}

int main()
{
    auto widget = Widget::make(some_args);
    // ...
}

However, the three-line Widget::make function is ugly — it's the single "blessed" way to create Widget objects, but we cannot make the Widget constructor private because Widget::make is implemented in terms of make_unique.

However, if we could make our Widget constructor itself create "blessed" objects, we could change main() to

int main()
{
    auto widget = std::make_unique<Widget>(some_args);
    // ...
}

and it would all Just Work, with no factory function.

The problem I'm having is that I believe we need the v8::HandleScope surrounding the construction of all those v8::Local<T>s inside create_v8_object(), which is called from our constructor's member-initializer-list. We need a way to create a HandleScope before executing the member-initializers, and then destroy it at the end of the constructor.

I'm looking for a way to solve this problem with a maximum of code clarity. (For example, I could add a member handleScope_ to the class, and make sure its member-initializer is the first in the list (well, the second, after isolate_); but that would bloat the size of the class and I wouldn't know how to clean it up at the end of the constructor.

I've also thought of moving the HandleScope from Widget::make down into Widget::create_v8_object(), where it would become an EscapableHandleScope and we'd return scope.Escape(gadget). But if I have lots of create_v8_object calls, would all those HandleScopes impose a performance penalty, or have any other bad effect?

Community
  • 1
  • 1
Quuxplusone
  • 23,928
  • 8
  • 94
  • 159

1 Answers1

1

How about delegating constructors?

class Widget {
private:
    Widget(v8::isolate && isol, v8::HandleScope scope, OtherArgs args) { /* ... */ }
    Widget(v8::isolate && isol, OtherArgs args) : Widget{std::move(isol), {isol.get()}, args} { }
public:
    Widget(OtherArgs args) : Widget{{}, args} {}
};
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • @Quuxplusone Do you like delegating constructors better? – T.C. Aug 29 '15 at 11:00
  • Hmm, I do like delegating constructors better. That seems like a reasonable solution. *In practice* I have a minor problem with it, in that I'd like something compatible with GCC 4.6 (delegating constructors arrived in 4.7); but from the language point of view, yes, delegating constructors solve the problem perfectly! – Quuxplusone Aug 30 '15 at 18:52