Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Temporary read-only copy of unique_ptr

I'm pretty new to C++11's smart pointers, and I'm trying to use them effectively in a project. In my project, I have a lot of functions that take a const reference to a vector of unique_ptr, do some computations on it, and place some results in a return parameter, like this:

void computeCoefficients(const vector<unique_ptr<Scalar>>& roots, 
    vector<unique_ptr<Scalar>>& coeffs) {
    ...
}

I'm using unique_ptr because the procedure calling all these functions is the sole owner of the objects in the vector, and the functions are just "borrowing" the objects in order to read them as input.

Now I'm trying to write a function that does computations on different subsets of the vector it receives, and in order to do that it needs to have different "versions" of the vector containing those subsets in order to pass to yet another function that takes a vector<unique_ptr<Scalar>> as input. But the only way to get a subset of a vector is to make a copy of it - which is a problem because unique_ptrs can't be copied. I'd like the code to look something like this:

void computeOnSet(const vector<unique_ptr<Scalar>>& set, unique_ptr<Scalar>& output) {  
    ...
}

void computeOnAllSubsets(const vector<unique_ptr<Scalar>>& set, vector<unique_ptr<Scalar>>& outputs) {
    for(int i = 0; i < input.size(); i++) {
        auto subset = vector<unique_ptr<Scalar>>(set.begin(), set.begin()+i);
        subset.insert(subset.end(), set.begin()+i+1, set.end();
        computeOnSubset(subset, outputs.at(i));
    }
}

Of course that doesn't work. I could make it work if I replaced the unique_ptrs with shared_ptrs, but that has two problems:

  • It would philosophically mean that I'm sharing ownership of the set with the computeOnSubsets function, and I'm not; the caller is still the sole owner. (I read that shared_ptr means you're sharing ownership with everything that has a copy of it).
  • It would introduce the overhead of reference-counting, even in places where I don't need it, because it would force me to change the input parameter of all my methods to vector<shared_ptr<Scalar>>.

All I want to do is make a temporary, read-only copy of a pointer, for the sole purpose of making temporary, read-only sub-vectors. Is there any way to do this? weak_ptr sounds like what I need (non-owning temporary pointer), but it can only be used with shared_ptr.

like image 393
Edward Avatar asked Apr 08 '13 19:04

Edward


1 Answers

I'm using unique_ptr because the procedure calling all these functions is the sole owner of the objects in the vector, and the functions are just "borrowing" the objects in order to read them as input.

Since the computing functions are not owning the pointed objects, just observing their state and making computations, you should pass them a vector of observing pointers (in this case, regular raw pointers), instead of a vector of unique_ptrs.

Since computeOnAllSubsets() and computeOnSet() are not responsible for the lifetime of the Scalar objects, they should not even acquire their ownership - in other words, they should not receive the owning unique_ptrs.

After all, it is guaranteed by the logic of your program that those functions won't be receiving dangling references, because the owning function won't destroy its vector before it has performed all the necessary computations. This is supported directly by what you are writing:

All I want to do is make a temporary, read-only copy of a pointer, for the sole purpose of making temporary, read-only sub-vectors. Is there any way to do this?

Just pass a vector of raw pointers to your computing functions. Given a unique_ptr, you can get access to the encapsulated raw pointer by calling the member function get():

std::unique_ptr<Scalar> pS // ... initialized somehow
Scalar* pScalar = pS.get();

As an alternative to raw pointers, you could use std::reference_wrapper for dispatching observing references. Especially during the process of refactoring a legacy code base where raw pointers are used for manual memory management, this would make it clear that ownership of the referenced objects belongs somewhere else.

Notice, however, that in Modern C++ a raw pointer is most often a synonym of observing pointers, so the above distinction is not really meaningful in a generalized context. The use case for which std::reference_wrapper is fundamental is when you want to pass objects by reference to some function template that accepts its arguments by value (std::bind() being a typical example).

like image 109
Andy Prowl Avatar answered Sep 25 '22 14:09

Andy Prowl