14

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.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
Edward
  • 5,942
  • 4
  • 38
  • 55
  • Are you sure you can't just have a `vector`? – Andy Prowl Apr 08 '13 at 19:47
  • Yes, because `Scalar` is actually an interface, not a concrete class. As far as I know the only way to use inheritance and polymorphism with STL containers is to use pointers, not value types. – Edward Apr 08 '13 at 19:49
  • Yes, that's correct. I though `Scalar` was a concrete, lightweight class. – Andy Prowl Apr 08 '13 at 19:50
  • 1
    You could make your functions accept iterators (which generalizes at the same time), then simply pass transform iterators that dereference that source iterators (Boost may have this already). – GManNickG Apr 08 '13 at 22:41
  • 1
    If `computeCoefficients` produces a `vector>& coeffs`, why doesn't it return it? Using the argument list for the return value is bad style. – MSalters Apr 09 '13 at 08:28
  • @MSalters: I was told by an experienced C++ developer that returning by arguments is better because it allows the caller to manage the memory that will be used for the return value. Also it's a superstitious holdover from C++03, where returning an object from a function always invoked an expensive copy. – Edward Apr 09 '13 at 15:13
  • @Edward: Even C++98 allowed (Named) Return Value Optimization. It's true that you can call `.reserve()` on `coeffs` before passing it in, but usually the callee knows better how many elements it will put in. – MSalters Apr 09 '13 at 15:46

2 Answers2

12

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).

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 2
    What about references, i.e. [reference wrappers](http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper)? I try to avoid raw pointers as it's not clear (from the type) what's the ownership concept. – dyp Apr 08 '13 at 20:53
  • @DyP: Yes, that's also a possibility. I believe anything that's observing is OK in this situation, and I personally consider using raw pointers only when they are observing - so the ownership concept is clear under this assumption that one should never use raw pointers as owning pointers. So IMHO reference wrappers and raw pointers are equally good. – Andy Prowl Apr 08 '13 at 21:08
  • @DyP: Hmm, reference wrappers look interesting, but can you use them with inheritance? (As I said in a comment above, Scalar is an interface). – Edward Apr 09 '13 at 01:44
  • Regarding Andy Prowl's answer, though, doesn't making my computing functions take `vector` require me to have my client program copy its vector before calling them? It would still have a `vector>`, which can't be passed to a function that expects `vector`, right? – Edward Apr 09 '13 at 15:07
  • @Edward: You end up creating a separate vector anyway in your function that creates the subset (see the code in your question). The only difference is that instead of creating a separate vector of `unique_ptr`s, you should create a separate vector of `Scalar*` (or `reference_wrapper`, if your prefer). – Andy Prowl Apr 09 '13 at 15:13
  • 1
    @DyP, the ownership of a raw pointer is clear: you don't own it. If you owned it you'd have a smart pointer. `reference_wrapper` is for passing things through functions that decay arguments, it is not a "dumb smart pointer" – Jonathan Wakely Apr 09 '13 at 16:52
  • 1
    @JonathanWakely I had [this GotW](http://herbsutter.com/2012/06/05/gotw-105-smart-pointers-part-3-difficulty-710/) in mind, and some experience with programmers/libraries who don't like smart pointers. Before the advent of smart pointers, there only were raw pointers, so I don't see how a raw pointer tells anything about ownership - You can implement this (don't own) as a guideline in a code base where smart pointers are extensively used, which I'd advocate. It's clear you don't own a reference, but you can't store them in containers, therefore `reference_wrappers`. – dyp Apr 09 '13 at 17:12
  • 1
    @DyP: I think I'm going to use `reference_wrapper`s because they're much clearer about ownership semantics, and the project I'm doing involves upgrading code from an old codebase that uses raw pointers everywhere (so someone looking at my code might think raw pointers are still being used as owning pointers). If you wrote that as an answer I'd accept it... – Edward Apr 11 '13 at 14:15
  • @Edward: I elaborated a bit at the end of the answer – Andy Prowl Apr 11 '13 at 14:25
3

Using iterator might make your code much more generic. Just declare your computation function like:

template <typename InputIt, typename OutputIt>
void compute(InputIt first_it, InputIt last_it, OutputIt d_first, Output d_last);

This computation function template handles data in range [first_it, last_it), and then put the result into [d_first, d_last). It doesn't care the type of input or output containers. The iterators act as pointers to the elements in container, which is the thought of STL.

Furthermore, in some case, you don't even go through the range manually. Just use function templates in <algorithm>, such as std::for_each and std::transform.

I don't really understand the code snippet in computeOnAllSubsets, but instinctively I think that std::transform might be helpful.

Matt Yang
  • 661
  • 4
  • 8
  • +1 First I thought there'd be a problem with the `unique_ptr`s, but if you use `const_iterators`, it should be fine. – dyp Apr 11 '13 at 14:40
  • I don't think so. C++ standard doesn't limit that [InputIterator Concept](http://en.cppreference.com/w/cpp/concept/InputIterator) should be constant. So, following the declaration in standard might be more general, I guess. – Matt Yang Apr 12 '13 at 15:01
  • For `std::unique_ptr`, by the way, I agree with you. It's not a good idea to put `std::unique_ptr` into containers because lots of unreadable compilation errors might be given while using STL functions, such as `std::sort(std::begin(v), std::end(v), std::greator())`. It means losing STL features if doing so. – Matt Yang Apr 12 '13 at 15:31
  • 1
    Ad 1) Yes, but you don't want the compute function to change ownership. Granting access to non-const `unique_ptr`s would allow that. Ad 2) You need a comparison function that deferences the pointer, same for `shared_ptr` or raw pointers. It's possible and useful to put `unique_ptr`s in standard containers (that's one of the reasons they replaced `auto_ptr`). – dyp Apr 12 '13 at 17:09
  • 1) Iterators act as pointers but not real pointers actually. It's a value type indeed. So, a const iterator doesn't mean its pointed element is const. 2) It depends. Maybe std::sort will never be used :). I think the reason to replace auto_ptr is putting unique_ptr into containers might get compilation errors, while runtime errors for auto_ptr. – Matt Yang Apr 12 '13 at 23:26
  • Ad 1) constant iterators are _immutable_, though I can't find much info besides that in the Std. But AFAIK that means the object it refers to is const (that is, not mutable using this iterator), and therefore, you cannot take ownership of the `unique_ptr` the iterator refers to. Ad 2) ?? The problem I referred to is that you have to provide a custom comparison function _in every case_ if you have a container of pointers/indirections of any kind (and you want to compare the objects referred to by the indirections). – dyp Apr 12 '13 at 23:42
  • 1) OK. Just try `template void access(const InputIt it) {*it = blabla;}` and check whether it can modify the element or not. 2) Aha, yes, agree. But what I tried to emphasize is whether the custom comparison function **could copy pointers or not**. For the ones to handle unique_ptr, it's forbidden by compiler. For ones to handle auto_ptr, it might cause runtime error if copied. For ones to handle shared_ptr or raw pointers, copy them anyway. And the most important thing is that perhaps the custom functions are not provided by you in lots of cases. – Matt Yang Apr 13 '13 at 01:26
  • 1) There's a difference between a `const Iterator` type and a `const_iterator`; basically it's the same as `int* const` vs. `int const*`. I refer to calling `compute` using `const_iterator`s, e.g. via `cbegin`. 2) "And the most important thing is that perhaps the custom functions are not provided by you in lots of cases." I still don't get what you mean, sry. You _have_ to provide the comparison function in _any way_, with _any_ kind of pointer. I know and agree about copying of pointers, though I think the comparison function should not interfere with ownership at all. – dyp Apr 13 '13 at 18:26
  • 1) Aha, you said const_iterator. My fault, sorry for that. Even so, I still don't think it's better than InputIt. Just check the declaration of std::transform or other algorithm. 2) I was talking about a general scenario. Maybe another project group wrote the comparison function. And it would be the hell if it were an API of a commercial library. – Matt Yang Apr 14 '13 at 01:49
  • 1) What I meant was not changing the signature of `compute`, but calling it using `const_iterators`, like `compute(myVector.cbegin(), myVector.cend(), myResult.begin(), myResult.end())`. 2) ? Wrap their comparison function in a lambda (like `[](It, It){ return direct_compare(*It, *It); }`). Don't expose any Standard Library containers via a DLL interface (the StdLib implementation used to build the DLL may be different from the StdLib implementation of the user of the DLL). – dyp Apr 14 '13 at 13:36
  • 1) Got it. 2) The cons of put `std::unique_ptr` into container have, that whether there are compilation errors might depend on the implementation of the custom comparison function(forbidden copying) which perhaps were a bad smell(especially in case of 3rd-party API), and that most of C++ compilers might give lots of compilation errors which are too unreadable to understand when custom comparison function copies std::unique_ptr. – Matt Yang Apr 14 '13 at 14:18
  • 2) Yes, template compilation errors are nasty, but they shouldn't be a reason to avoid certain uses IMHO. Rather, the compiler should make them readable (yet, the problem here arises from the StdLib, not the core language, therefore it's difficult for the compiler). Regarding the comparison function: [example code](http://ideone.com/7gY4A5) – dyp Apr 14 '13 at 15:49
  • Yes, business for compiler, but it still makes our debugging hard if lots of god damned code is on your hand :). Anyway, just a suggestion, taking or not depends on your situation. I will not do this unless there must have a very good requirement indeed because I hate these cons. But maybe on the other hand, cons are not cons for you. – Matt Yang Apr 15 '13 at 02:06
  • All this talk about avoid the STL algorithms when using containers with move-only types is nonsense – `std::sort` et al _move_ objects around, not copy them. Sorting a vector of unique_ptrs is not a problem in the least, it's a totally normal scenario. That said, while I disagree with most of your comments, +1 for your answer. – ildjarn Apr 18 '13 at 23:07