2

I have code that looks like:

vector<unique_ptr<Foo>> foos = ... // Init vector.

// Do some stuff.

func(std::move(foos)); // func should now own foos, I'm done with them.
// Takes ownership of foos.
void func(vector<unique_ptr<Foo>>&& foos);

Now inside func I want to refactor out a bit of code into a separate function that needs to use foos. I am wondering how to do this. The options I considered are:

void util_func1(const vector<Foo*>& foos); // Does not take ownership of foos
void util_func2(const vector<unique_ptr<Foo>>& foos); // Does not take ownership of foos

The first option seems in line with the recommendation for (non-vectors of) unique_ptr, if a function takes ownership, pass unique_ptr by value, if a function doesn't take ownership pass by raw ptr/ref. If I understand correctly, the recommendation is to never pass unique_ptr by const-ref. Which is essentially what util_func2 is doing.

My problems is that now func has gotten pretty ugly:

void func(vector<unique_ptr<Foo>>&& foos) {
  vector<Foo*> raw_ptr_foos;
  raw_ptr_foos.reserve(foos.size());
  for (auto foo : foos) raw_ptr_foos.push_back(foo.get());
  util_func1(raw_ptr_foos);
}

So is util_func2 the correct way to do this or should I bite the bullet and write the ugly conversion to raw_ptr_foos or is there a 3rd way?

Aamir
  • 1,974
  • 1
  • 14
  • 18
Benjy Kessler
  • 7,356
  • 6
  • 41
  • 69
  • With `std::ranges`, you might take a view of `Foo`: with `transform`, you have a lazy view instead of a copy. – Jarod42 Oct 31 '22 at 13:12
  • 4
    The `func` does not pass pointers, it passes a vector of pointers. So rules related to vector should be applied - pass by `const vector>&`. All the important properties related to ownership are preserved - for example, the passed vector cannot be copied (`unique_ptr` value type forbids) or moved (because it's `const`). – smitsyn Oct 31 '22 at 13:14
  • Fyi, `void func(vector>&& foos);` isn't "taking ownership" of *anything*, so that comment is not accurate. – WhozCraig Oct 31 '22 at 13:25
  • 2
    Just change `func` to `void func(std::vector> foos)` as it's a _"sink function"_ - live - https://godbolt.org/z/KW7xjqjPz – Richard Critten Oct 31 '22 at 13:40
  • @WhozCraig, why isn't it taking ownership? Won't all the foos get deleted once `func` completes? – Benjy Kessler Oct 31 '22 at 13:48
  • @RichardCritten, oh I didn't think that would compile since unique_ptrs can't be copied... C++ is too complicated... – Benjy Kessler Oct 31 '22 at 13:50
  • @BenjyKessler it's just the vector's internal that get moved (2 pointer's or pointer and length) nothing happens to the unique_ptrs – Richard Critten Oct 31 '22 at 13:52
  • @BenjyKessler no, there's no guarantee of any "deletion", until `foos` goes out of scope. – JHBonarius Oct 31 '22 at 14:58

2 Answers2

0

If you have C++20, you express it as concepts, and have a constrained template as your helper.

template <typename P, typename T>
concept points_to = requires(P p) {
    { *p } -> std::common_reference_with<T &>
} && std::equality_comparable_with<std::nullptr_t>

template <std::ranges::borrowed_range R, typename T>
concept indirect_range_of = points_to<std::ranges::range_value_t<R>, T>;

template <indirect_range_of<Foo> Foos>    
void util_func1(Foos foos);

void func(std::vector<std::unique_ptr<Foo>> foos) {
  util_func1(std::ranges::views::all(foos));
}

Prior to that you write a template, and optionally add sfinae or static_asserts

template <typename Foos
void util_func1(const Foos & foos);

void func(std::vector<std::unique_ptr<Foo>> foos) {
  util_func1(foos);
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
-1

If you want to access and change these vector elements in multiple functions, I would consider using shared_ptrs instead of unique_ptrs. This way you still don't have to worry about memory leaks at the end of function execution and you don't have to mess around with raw pointers.

Job van Ellen
  • 1
  • 1
  • 1
  • 3
  • Why would that be needed? `shared_ptr` is unnecessarily heavy and only required in very niche situations. Which doesn't seem here. – JHBonarius Oct 31 '22 at 14:59
  • I didn't say it was needed. The stated problem is that is function is ugly. Using shared_ptrs will remove the need for the raw pointer conversions and make the function look cleaner. As I understand it the overhead of shared_ptr is still very limited: https://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c. Also, I know nothing about the performance requirements of this program, so I did not take that into account. – Job van Ellen Oct 31 '22 at 15:06
  • You're unnecessarily fixing the wrong choice by the OP. He mas a misconception about what he's doing, and using `void util_func2(const vector>& foos);` would be fine (already stated in the comments). So no "ugly" conversion is required. – JHBonarius Oct 31 '22 at 15:09