Custom transferable-ownership classes

Discussion in 'C++' started by mrts, Aug 16, 2010.

  1. mrts

    mrts Guest

    First, a bit of high-level context:

    I'm using JNI in a C++ project. Each JNI resource
    should be accessed via a reference to the resource
    object inside the Java virtual machine (JVM)
    environment. That reference needs to be released
    with when the resource is no longer needed.

    E.g. to create a string, one would call

    jstring str_ref = env_->NewStringUTF("foo");

    where env_ is the pointer to the JVM environment,
    and to release the resources of the string,

    _env->DeleteLocalRef(str_ref);

    This is a classic case of RAII. A smart "handle"
    that would release the reference in it's
    destructor would be a perfect, exception-safe
    casing for the naked JVM references.

    So far, so good.

    In the spirit of proper encapsulation, all of the
    JNI machinery is designed to be hidden behind a
    high-level wrapper class, JNIWrapper. This is a
    singleton class that owns all the JVM-related
    resources, including the JVM environment pointer.

    New resources should be created according to the
    source/sink idiom [1], hiding the private
    environment pointer:

    JVMRef<jstring> JNIWrapper::newString(const char* str)
    {
    // env_ is a private member of the JNIWrapper
    // instance
    JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
    return ret;
    }

    where JVMRef should be an auto_ptr like
    transferable-ownership class that needs a custom
    deleter that has access to the environment. (Note
    that I cannot use C++0X's std::unique_ptr.)

    Here's a simple, auto_ptr-like first stab at
    JVMRef:

    template <typename T>
    class JVMRef {
    public:
    JVMRef(JNIEnv *env, T ref) :
    env_(env), ref_(ref) { }

    JVMRef(JVMRef& other) :
    env_(other.get_env()), ref_(other.release()) { }

    JVMRef& operator=(JVMRef &other)
    { reset(other.release()); return *this; }

    ~JVMRef() { reset(NULL); }

    T get()
    { return ref_; }

    JNIEnv *get_env()
    { return env_; }

    T release()
    { T ret = ref_; ref_ = NULL; return ret; }

    void reset(T other = NULL) {
    if (ref_ != other) {
    env_->DeleteLocalRef(ref_);
    ref_ = other;
    }
    }

    private:
    JNIEnv *env_;
    T ref_;
    };

    The "source" function is given in
    JNIWrapper::newString() above. Attempts to
    use it in a "sink" function to construct a new
    JVMRef to a Java string as follows:

    // jni is the instance of JNIWrapper
    JVMRef<jstring> jfoo(jni.newString("foo"));

    fail as follows with g++ 4.4.1:

    ---

    error: no matching function for call to
    ‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’

    note: candidates are:
    JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
    [with T = _jstring*]

    note:
    JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
    [with T = _jstring*]

    ---

    That's probably because the temporary that's created
    when the "source" returns can't be used by reference.

    How should I amend JVMRef to make it appease
    the compiler? (Btw, I'm aware of Boost's shared_ptr that
    supports deleters, but it isn't a neat fit.)

    [1] http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
     
    mrts, Aug 16, 2010
    #1
    1. Advertising

  2. mrts wrote:
    >
    > template <typename T>
    > class JVMRef {
    > public:
    > JVMRef(JNIEnv *env, T ref) :
    > env_(env), ref_(ref) { }
    >
    > JVMRef(JVMRef& other) :
    > env_(other.get_env()), ref_(other.release()) { }
    >
    > JVMRef& operator=(JVMRef &other)
    > { reset(other.release()); return *this; }
    >
    > ~JVMRef() { reset(NULL); }
    >
    > T get()
    > { return ref_; }
    >
    > JNIEnv *get_env()
    > { return env_; }
    >
    > T release()
    > { T ret = ref_; ref_ = NULL; return ret; }
    >
    > void reset(T other = NULL) {
    > if (ref_ != other) {
    > env_->DeleteLocalRef(ref_);
    > ref_ = other;
    > }
    > }
    >
    > private:
    > JNIEnv *env_;
    > T ref_;
    > };
    >
    > The "source" function is given in
    > JNIWrapper::newString() above. Attempts to
    > use it in a "sink" function to construct a new
    > JVMRef to a Java string as follows:
    >
    > // jni is the instance of JNIWrapper
    > JVMRef<jstring> jfoo(jni.newString("foo"));
    >
    > fail as follows with g++ 4.4.1:
    >
    > ---
    >
    > error: no matching function for call to
    > ‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’
    >
    > note: candidates are:
    > JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
    > [with T = _jstring*]
    >
    > note:
    > JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
    > [with T = _jstring*]


    Read what the compiler is telling you. You do not have constructor with
    next signature :

    JVMRef(SNI::JVMRef<_jstring*>);

    your constructors are :
    JVMRef(JNIEnv *env, T ref)
    JVMRef(JVMRef& other) <<< copy constructor

    What is the return of jni.newString("foo") ?
     
    Vladimir Jovic, Aug 17, 2010
    #2
    1. Advertising

  3. mrts

    mrts Guest

    On Aug 17, 11:05 am, Vladimir Jovic <> wrote:
    > mrts wrote:
    >
    > > template <typename T>
    > > class JVMRef {
    > > public:
    > >     JVMRef(JNIEnv *env, T ref) :
    > >         env_(env), ref_(ref) { }

    >
    > >     JVMRef(JVMRef& other) :
    > >         env_(other.get_env()), ref_(other.release()) { }

    >
    > >     JVMRef& operator=(JVMRef &other)
    > >     { reset(other.release()); return *this; }

    >
    > >     ~JVMRef() { reset(NULL); }

    >
    > >     T get()
    > >     { return ref_; }

    >
    > >     JNIEnv *get_env()
    > >     { return env_; }

    >
    > >     T release()
    > >     { T ret = ref_; ref_ = NULL; return ret; }

    >
    > >     void reset(T other = NULL) {
    > >         if (ref_ != other) {
    > >             env_->DeleteLocalRef(ref_);
    > >             ref_ = other;
    > >         }
    > >     }

    >
    > > private:
    > >     JNIEnv *env_;
    > >     T ref_;
    > > };

    >
    > > The "source" function is given in
    > > JNIWrapper::newString() above. Attempts to
    > > use it in a "sink" function to construct a new
    > > JVMRef to a Java string as follows:

    >
    > >     // jni is the instance of JNIWrapper
    > >     JVMRef<jstring> jfoo(jni.newString("foo"));

    >
    > > fail as follows with g++ 4.4.1:

    >
    > > ---

    >
    > > error: no matching function for call to
    > > ‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’

    >
    > > note: candidates are:
    > > JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
    > > [with T = _jstring*]

    >
    > > note:
    > > JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
    > > [with T = _jstring*]

    >
    > Read what the compiler is telling you. You do not have constructor with
    > next signature :
    >
    > JVMRef(SNI::JVMRef<_jstring*>);
    >
    > your constructors are :
    > JVMRef(JNIEnv *env, T ref)
    > JVMRef(JVMRef& other)       <<< copy constructor
    >
    > What is the return of jni.newString("foo") ?


    A temporary, as shown above:

    JVMRef<jstring> JNIWrapper::newString(const char* str)
    {
    // env_ is a private member of the JNIWrapper
    // instance
    JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
    return ret;
    }

    ---

    I do understand that the temporary is the culprit here
    and that there is no constructor that accepts JVMRef
    by value.

    I've examined the definition of auto_ptr meanwhile
    and found the trickery of auto_ptr_ref that makes
    auto_ptr work correctly in the source/sink scenario.

    So, sorry for the fuss and for not thinking with
    the required rigour :). I've settled for the following
    to provide the functionality needed:

    ---

    template <typename T>
    struct jvmref_ref
    {
    explicit jvmref_ref(JNIEnv *e, T p) : _env(e), _ptr(p) { }

    JNIEnv *_env;
    T _ptr;
    };

    template <typename T>
    class JVMRef {
    public:
    JVMRef(JNIEnv *env, T ref) :
    _env(env), _ref(ref) { }

    JVMRef(JVMRef& other) :
    _env(other.get_env()), _ref(other.release()) { }

    JVMRef(jvmref_ref<T> ref) : _env(ref._env), _ref(ref._ptr) { }

    operator jvmref_ref<T>()
    { return jvmref_ref<T>(this->get_env(), this->release()); }

    ~JVMRef() { reset(NULL); }

    T get()
    { return _ref; }

    JNIEnv* get_env()
    { return _env; }

    T release()
    { T ret = _ref; _ref = NULL; return ret; }

    void reset(T other = 0)
    {
    if (_ref != other) {
    _env->DeleteLocalRef(_ref);
    _ref = other;
    }
    }

    private:
    JVMRef& operator=(const JVMRef&);
    JNIEnv *_env;
    T _ref;
    };

    ---

    That solves the general problem of how should one handle factory
    functions that would otherwise use auto_ptr, but need a custom deleter
    and can not use std::unique_ptr or boost::shared_ptr.

    (Any insight on the wrongs of the above is of course much welcome.)
     
    mrts, Aug 17, 2010
    #3
  4. mrts wrote:
    >
    > JVMRef<jstring> JNIWrapper::newString(const char* str)
    > {
    > // env_ is a private member of the JNIWrapper
    > // instance
    > JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
    > return ret;
    > }
    >


    >
    > The "source" function is given in
    > JNIWrapper::newString() above. Attempts to
    > use it in a "sink" function to construct a new
    > JVMRef to a Java string as follows:
    >
    > // jni is the instance of JNIWrapper
    > JVMRef<jstring> jfoo(jni.newString("foo"));
    >
    > fail as follows with g++ 4.4.1:
    >
    > ---
    >
    > error: no matching function for call to
    > ‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’
    >
    > note: candidates are:
    > JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
    > [with T = _jstring*]
    >
    > note:
    > JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
    > [with T = _jstring*]
    >


    Sorry to be PITA again, but the compiler complains about
    JVMRef<_jstring*> type, and your bits and pieces of the example are
    showing you used JVMRef<jstring>.

    If you copy and paste the fully compilable example (including main), you
    will get much better response :
    http://www.parashift.com/c -faq-lite/how-to-post.html#faq-5.8


    > That's probably because the temporary that's created
    > when the "source" returns can't be used by reference.


    Is that legal?
     
    Vladimir Jovic, Aug 17, 2010
    #4
  5. mrts

    mrts Guest

    On Aug 17, 12:54 pm, Vladimir Jovic <> wrote:
    > mrts wrote:
    >
    > > JVMRef<jstring> JNIWrapper::newString(const char* str)
    > > {
    > >     // env_ is a private member of the JNIWrapper
    > >     // instance
    > >     JVMRef<jstring> ret(env_, env_->NewStringUTF(str));
    > >     return ret;
    > > }

    >
    > > The "source" function is given in
    > > JNIWrapper::newString() above. Attempts to
    > > use it in a "sink" function to construct a new
    > > JVMRef to a Java string as follows:

    >
    > >     // jni is the instance of JNIWrapper
    > >     JVMRef<jstring> jfoo(jni.newString("foo"));

    >
    > > fail as follows with g++ 4.4.1:

    >
    > > ---

    >
    > > error: no matching function for call to
    > > ‘JNI::JVMRef<_jstring*>::JVMRef(JNI::JVMRef<_jstring*>)’

    >
    > > note: candidates are:
    > > JNI::JVMRef<T>::JVMRef(JNI::JVMRef<T>&)
    > > [with T = _jstring*]

    >
    > > note:
    > > JNI::JVMRef<T>::JVMRef(JNIEnv*, T)
    > > [with T = _jstring*]

    >
    > Sorry to be PITA again, but the compiler complains about
    > JVMRef<_jstring*> type, and your bits and pieces of the example are
    > showing you used JVMRef<jstring>.
    >
    > If you copy and paste the fully compilable example (including main), you
    > will get much better response :http://www.parashift.com/c -faq-lite/how-to-post.html#faq-5.8


    Yes, sorry for that... as for jstring, it's defined as follows in
    Sun's jni.h:

    class _jstring : public _jobject {};
    typedef _jstring *jstring;

    > > That's probably because the temporary that's created
    > > when the "source" returns can't be used by reference.

    >
    > Is that legal?


    No, and that was exactly the problem. To overcome this, the
    struct jvmref_ref trick is needed (the trick lies in defining
    `JVMRef(jvmref_ref<T> ref)` and `operator jvmref_ref<T>()`
    and it is a shameless copy from std::auto_ptr).
     
    mrts, Aug 17, 2010
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Brian
    Replies:
    0
    Views:
    392
    Brian
    May 3, 2005
  2. 6e
    Replies:
    3
    Views:
    415
    Roedy Green
    Oct 25, 2005
  3. Mike

    Tomcat file ownership

    Mike, Feb 28, 2006, in forum: Java
    Replies:
    0
    Views:
    367
  4. David Rasmussen

    Vector and ownership

    David Rasmussen, Jul 11, 2003, in forum: C++
    Replies:
    0
    Views:
    335
    David Rasmussen
    Jul 11, 2003
  5. tarmat

    ownership style question

    tarmat, Nov 24, 2003, in forum: C++
    Replies:
    4
    Views:
    407
    lilburne
    Nov 24, 2003
Loading...

Share This Page