matthewaveryusa
matthewaveryusa

Reputation: 652

Is this approach of moving data from standard containers to shared pointers correct?

Here is my problem: sometimes I have an std::string defined in a function. I need to extend the scope of the string because of an asynchronous operation. Naturally I can copy it into a shared pointer like this:

{
std::string hi{"hi"};
auto shared_hi = std::make_shared<std::string>(hi);
//insert shared_hi in async queue
}

The problem with this is that sometimes my strings are very very very big, and sometimes they are vectors, and sometimes they are std::arrays. so not only would I like to avoid a copy, but I would also like to have a function that can "steal" the data out of containers without having to copy them. I've come up with a clever solution posted below, but I'm wondering if there isn't a better solution to this. If there isn't, I'd like to know if doing what I'm doing below is defined behavior:

template<class T>
class WrappedDeleter {
public:
  WrappedDeleter(T  &&other): o_(std::move(other)) {
  }
private:
  T o_;
};

//This function creates a deleter where the scope
template<class P, class T>
std::function<void(P *)> make_delete(T  &&encapsulate) {
  WrappedDeleter<T> *d = new WrappedDeleter<T>(std::move(encapsulate));
  return [d](P * ptr) {
    std::cout << "deleting now\n";
    delete d;
  };
}

template<class P, class C>
std::shared_ptr<P> steal_data(C  &&data) {
  P *ptr = data.data();
  //can't use make_shared - custom deleter
  return std::shared_ptr<P>(ptr, make_delete<P, C>(std::move(data)));
}

used like this:

int main() {
    {
        std::shared_ptr<int> p_a;
        std::shared_ptr<int> p_b;
        std::shared_ptr<const char> p_c;

        {
            std::array<int,3> a= {{1,2,3}};
            std::vector<int> b= {1,2,3};
            std::string c= {"hello world"};
            p_a = steal_data<int,std::array<int,3> >(std::move(a));
            p_b = steal_data<int, std::vector<int> >(std::move(b));
            p_c = steal_data<const char, std::string>(std::move(c));
            std::cout << "inside" << *p_a << " " << *p_b << " " << p_c.get() << std::endl;
        }
        std::cout << "outside" << *p_a << " " << *p_b << " " << p_c.get() << std::endl;


        std::cout << "end of scope\n";
    }
    return 0;
}

Upvotes: 2

Views: 229

Answers (1)

ecatmur
ecatmur

Reputation: 157414

As Praetorian says, the only sensible way to move data into a shared_ptr is using make_shared<T>(move(obj)). If you want the shared_ptr to point to the underlying contiguous data block rather than the container itself, you can use the alias constructor template<class Y> shared_ptr(const shared_ptr<Y>& r, T *ptr);:

std::vector<int> v{1, 2, 3};
auto cptr = std::make_shared<std::vector<int>>(std::move(v));
std::shared_ptr<int> ptr{cptr, cptr->data()};

As a function:

template<typename Container>
std::shared_ptr<Container::value_type> steal_data(Container &&cont) {
    auto cptr = std::make_shared<Container>(std::move(cont));
    return {cptr, cptr->data()};
}

Upvotes: 2

Related Questions