2

I am facing the following problem. I have a protected data structure a map of <ID , Object>. There is only one map data structure and multiple threads might want to set and get values from the map using an ID. The Object could be nested as shown in the following sample code.

#include <iostream>
#include <map>
#include <algorithm>
#include <mutex>
using namespace std;
struct C {
    int r;
    char s;
};
struct B {
    int p;
    char q;
    C c;
};
struct A {
    int x;
    int y;
    B b;
    char z;
};
/*
    A
    L__ x
    |
    L__ y
    |
    L__ B
        L__p
        |
        L__q
        |
        L__C
            L__r
            |
            L__s

*/
/*  the follwing is the
    data structure to store objects of of type A 
    corresponding to an ID (int)
*/
map<int,A> Map;
mutex m;

/*  Follwing are the desired 
    api's to set and get values from the Map 
    Locks are also required to handle concurrency
*/
void getValueFromMap(int id,/* what to pass ?? */) {
    m.lock();

    /* code to get the value of the specified varible */

    m.unlock();
}
void setValueInMap(int id,/* what to pass ?? */) {
    m.lock();

    /* code to set the new value to the specified variable */

    m.unlock(); 
}
int main() {
    /* lets suppose Map has some A type objects already */
    int id = 4;
    /* assume I want to change the information (value of p) of  id = 4*/
    /* instead of doing the following */
    m.lock();
    Map[id].b.p = 12; /*update to some value */
    m.unlock();
    /* what I need the follwing */
    setValueInMap(4,/* what_to_change , what_is_the_new_value etc .. */);

    /*similarly */

    /*if I want get the value of s */
    int s;
    m.lock();
    getValueFromMap(id,/* what_to_get,placeholder (for example &s) */);
    m.unlock();
}

I want to have some api calls ( for example setValueInMap with fixed number of arguments ) to set and get values of the map using function calls and all the mutex work will happen inside those api calls only. Although I have shown only one generic set function to set all types of member variables of struct A, it need not be (more api function calls is of no problem).

How it can be implemented?

thanks!

Debashish
  • 1,155
  • 19
  • 34
  • 2
    What about existing thread safe lock free map, it is usually based on skip list data structure. [libcdc](https://github.com/khizmax/libcds) have really good implementation. – Victor Gubin Jan 03 '19 at 18:17

4 Answers4

5

How about making a generic function to act on map:

std::map<int, A> Map;
std::mutex m;

template <typename F>
auto ActOnMap(F&& f) -> decltype(f(Map)) {
    std::lock_guard<std::mutex> locker(m);
    return f(Map);
}

(Note: you should probably create a class to structure that and hide direct access to map and mutex.)

And then:

/* lets suppose Map has some A type objects already */
int id = 4;
/* assume I want to change the information (value of p) of  id = 4*/
ActOnMap([id](std::map<int, A>&m){ m[id].b.p = 12;});

/*similarly */

/*if I want get the value of s */
int s = ActOnMap([id](std::map<int, A>&m){ return m[id].b.c.s;});
Jarod42
  • 203,559
  • 14
  • 181
  • 302
3

The main question is how you want to encode what you call the "placeholder" into the C++ type system. To choose from:

  • Plain indices (0 -> A.x, 3 -> B.q, etc.) and some hardcoded switch statement (very fragile, not recommended).

  • The same, but with an enum instead of indices. Not much better than above.

  • Member variable pointers. This will get very awkward, especially due to the nested structure.

  • Functions. You can write access functions for each (nested) member and pass function pointers to those (or functors, e.g. lambdas). Something like this:

    void setBp(A& a, int value) { a.b.p = value; }
    // etc.
    
    template<typename T, typename SetterFunc>
    setValueInMap(int key, SetterFunc setterFunc, T value) {
      m.lock();
      setterFunc(Map[id], value);
      m.unlock();
    }
    
    // Usage
    setValueInMap(4, setBp, 12);
    

Note how none of these approaches is very pretty. At all. I'd look into changing the underlying design instead, the nested structure is really the root of this issue.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • Yes, thanks. I also thought of having a function pointer table pointing to getter and setter functions for each member variables. I was not sure how to do it. – Debashish Jan 03 '19 at 18:35
  • `nested structure is really the root of this issue.` Why could be a better option ? – Debashish Jan 03 '19 at 18:35
  • 2
    Possibly with member variable pointers and variadic template: `setValueInMap(42, 12, &A::b, &B::p)` – Jarod42 Jan 03 '19 at 20:12
  • 1
    @Debashish: Something like [that](http://coliru.stacked-crooked.com/a/18eb510e5483b22e) – Jarod42 Jan 03 '19 at 23:27
  • @Max Langhof why the switch case method is "very fragile, not recommended" ? – Debashish Jun 26 '19 at 14:25
  • 1
    @Debashish If members are added, removed, reordered or changed in any other way, you have to update every switch statement carefully. If you make a mistake with the indices the compiler has no chance to warn you. – Max Langhof Jun 26 '19 at 15:13
1

Maybe you could go for a RapidJson approach or an if based (less elegant) approach:

(...)

void* getValueFromMap(int id, char structure, string item_name) {
    m.lock();

    if(structure == 'C') {
         if(item_name.compare("r") == 0) {
              return (void*) Map[id].B.C.r;
         } else if(...

    } else if(...

    m.unlock();
}
void setValueInMap(int id, char structure, string item_name, void* item) {
    m.lock();

    if(structure == 'C') {
         if(item_name.compare("r") == 0) {
              Map[id].B.C.r = (int) item;
         } else if(...

    } else if(...


    m.unlock(); 
}

int main() {
    int r;
    r = (int) getValueFromMap(4,'C',"r");

    setValueInMap(5,'C',"r",(void*) r);
}

Disclaimer: I didn't compile this code, so it may have errors.

Grifo
  • 71
  • 1
  • 10
  • This is not tackling the issue. The question is about setting only specific members of each item. – Max Langhof Jan 03 '19 at 18:17
  • Thanks! but.while `setting` values I might not have the complete object. Similarly, I may not want to copy the entire structure into a local copy outside. Because having a local copy of `A` in a `thread` is not a good idea for concurrency IMHO. – Debashish Jan 03 '19 at 18:21
  • 1
    Sorry, I didn't quite get it at first. I edited the answer and I hope it better answers it now. – Grifo Jan 03 '19 at 18:44
  • 1
    Of course, this approach may be very time consuming for very big and nested structures. – Grifo Jan 03 '19 at 18:47
1

One way you could easily generate lambdas or functions to access nested members is to write a function or script to generate them in a scripting language.

# Ruby
def dig_lambda(*members)
  dig = "Map[id].#{members.join '.'}"  
  puts "[&]() -> decltype(#{dig}) {return #{dig};}" 
end

# prints [&]() -> decltype(Map[id].b.c.r) {return Map[id].b.c.r;}
dig_lambda %w[b c r]
Ray Hamel
  • 1,289
  • 6
  • 16