c++vectorjava-native-interface

What is the best container to store an {arbitrary jobject}'s data in C++?


I am writing an app that should use jobject's fields multiple times at a great speed. Since retrieving fields from jobjects is pretty slow, I want to do it once and then read and write fields of some C++ object or elements of a multitype array (if one can be created) or whatever that provides rapid data access (hereinafter - target container).

My code has two functions: first writes jobject fields into the target container, second reads the written data from it.

Target container should be capable of keeping variables of different type, and it's size must not be compile-time-defined (so struct is not suitable). The question is: what is the best container satisfying this conditions, if quality of it is defined by speed of reading (+converting types to proper ones) from it?

Right now, I use a vector<any> like below:

#include <vector>
#include <any>
JNIEXPORT void JNICALL Java_runTask(JNIEnv*env,jclass,jobject o){
    vector<any>d;
    readData(d,/*some other arguments*/,o);
    do{
        //do something useful
    }while(predicate);
}

void readData(vector<any>*data/*some other arguments*/,jobject o){
    //fill the vector with jobject's fields' values
}

bool predicate(vector<any>*data){
    //determine the return value using vector contents
    return true;
}

vector<any> meets my conditions, BUT...

  1. I am not sure that vector is the fastest container to read from.
  2. any_cast<type>() is CHECKING the type before case, which means that it has imperfect performance.

Solution

  • Rather than trying to use a container that can hold arbitrary data, we can wrap the data read and use in a polymorphic type.

    As a sketch, you would rework Java_runTask to not have to deal with the data directly.

    struct BaseTask
    {
        virtual bool predicate() = 0;
        virtual Result process() = 0; // whatever "do something useful" does
    };
    
    std::unique_ptr<BaseTask> readData(JNIEnv* env, jclass c, jobject o)
    {
        // based on c, construct a subclass of BaseTask, and read the data from o into it
    }
    
    JNIEXPORT void JNICALL Java_runTask(JNIEnv*env,jclass,jobject o){
        auto task = readData(/*some other arguments*/,o);
        do{
            auto res = task->process();
            // do more processing
        } while(task->predicate());
    }
    

    You then need to have subclasses of BaseTask specific to your different kinds of data. One way of doing that is having a template subclass

    template <typename T>
    struct Task : public BaseTask
    {
        T data;
        bool predicate() override;
        Result process() override; 
    };
    
    template <>
    bool Task<Foo>::predicate()
    {
        // data is a Foo here
    }
    
    template <>
    bool Task<Bar>::predicate()
    {
        // data is a Bar here
    }
    

    That's basically the same as defining

    class FooTask : public BaseTask { /* all the members */ };
    class BarTask : public BaseTask { /* all the members */ };