efficient / libcuckoo

A high-performance, concurrent hash table
Other
1.61k stars 275 forks source link

How to upsert a pointer? #67

Closed domino-succ closed 7 years ago

domino-succ commented 7 years ago

For example, I have a cuckoomp<string, shared_ptr<class>> mp;

I have to update like this:

void my_update (int num) {
    shared_ptr<class> p(new class);
    auto fn = [num](shared_ptr<class>& p){ ***  };
    mp.upsert(key, fn, p);
}

The problem is, every time when my_update is invoked, it new a class, even though this class might not be used. How can I avoid this?

manugoyal commented 7 years ago

The type signature of upsert is flexible enough to allow you to pass in just the constructor arguments you need, and it will call the shared_ptr constructor with whatever you pass it if the insert occurs. This is essentially the behavior of emplace in the STL containers.

Unfortunately, shared_ptr doesn't seem to have a constructor that will allocate a new pointer for you, it can only take ownership of existing pointers or shared_ptrs. So I'm not sure if there's a super clean solution to avoid constructing a shared_ptr in the case where the key is already in the table.

One way I can think of is to create a wrapper class around the shared_ptr, whose default constructor allocates the shared_ptr with a new instance of class. So you could have something like

struct shared_ptr_wrapper {
    shared_ptr<class> ptr;
    shared_ptr_wrapper() : ptr(new class) {}
};

Then you could write your function as

void my_update (int num) {
    auto fn = [num](shared_ptr_wrapper& p){ ***  };
    mp.upsert(key, fn);
}

And if the map needs to insert the key, it'll call the shared_ptr_wrapper constructor with no arguments and construct the shared_ptr the way you want.

domino-succ commented 7 years ago

it will call the shared_ptr constructor with whatever you pass it if the insert occurs

What if I want to new a class with a parameter, say 6. Like new class(6). How can I pass 6 to the constructor in upsert.

manugoyal commented 7 years ago

If you go with the wrapper class approach I outlined, you can create another constructor in the wrapper class that accepts an integer, and then just call upsert as mp.upsert(key, fn, 6). If you want to write one totally generic constructor, you can utilize perfect forwarding and write the shared_ptr_wrapper constructor as follows:

template <typename... Args>
shared_ptr_wrapper(Args&&... args) : ptr(std::make_shared<class>(std::forward<Args>(args)...))) {}

This constructor will essentially forward any of the arguments you pass in upsert to the class constructor.

domino-succ commented 7 years ago

Got you. I'll try that. Thanks