1

I'm trying to replace a function which searches a container of pointers and provides the found element as an outgoing reference by using unique_ptr.

Below is a simple example to demonstrate the situation when using plain pointers:

bool find_ref_in_list(int n, const std::list<int*>&l, int*& element)
{
    auto iter = find_if(l.begin(), l.end(), [n](int* x)
    {
        return (*x == n) ? true : false;
    });

    element = *iter;

    return true;
}

Trying to encode this by using unique_ptr leads me to the following code which causes an error when storing the result to the element reference.

bool find_ref_in_list(int n, const std::list<std::unique_ptr<int>>&l, std::unique_ptr<int>& element)
{
    auto iter = std::find_if(l.begin(), l.end(), [n](const std::unique_ptr<int>& x)
    {
        return (*x == n) ? true : false;
    });

    element = *iter; // causes error because assignment operator=

    return true;
}

In this situation I don't intend to share the ownership of the element, so shared_ptr is not an option. Also I do not want to take the ownership so a move operation isn't a good idea.

I'm aware that I could simply use an iterator as outgoing reference or the raw pointer by get(), but I think that would somehow obstruct the idea of unique_ptrs.

So the question if it is possible to provide the outgoing reference to a unique_ptr or which is the right technique to encode this problem for unique_ptrs. I'm looking especially for a solution which works without using 3rd party libraries like boost.

  • `return (*x == n) ? true : false;` looks like code obfuscation – Slava Aug 14 '19 at 13:22
  • See this [question](https://stackoverflow.com/questions/56779682/pass-c-vector-of-unique-ptrt-without-passing-ownership). – 1201ProgramAlarm Aug 14 '19 at 13:25
  • I see I have missed to add the case that the value was not found. so actually there should be a `if (iter == l.end()) return false;` right before the element assignment. – Achim Schön Aug 14 '19 at 14:43
  • Try `element.reset(iter->get()); ` in place of `element = *iter;` – seccpur Aug 14 '19 at 14:58
  • @seccpur actually that is a nice approach. Maybe you should rather post this as an answer than a comment since it really fulfills all demands. – Achim Schön Aug 14 '19 at 16:14

4 Answers4

2

I don't intend to share the ownership of the element, so shared_ptr is not an option. Also I do not want to take the ownership so a move operation isn't a good idea.

The current popular convention is that a raw pointer is just an observer. It doesn't participate in any kind of ownership of the object. It also shouldn't be stored anywhere that can possibly outlive the object it points to. It's fine to use raw pointers in such cases (in fact, many people recommend to use raw pointers for this.) So whenever you use a raw pointer in an API, it should always be a non-owning pointer that only provides access to an object owned by someone else.

However, if you want to be able to give the caller the option to actually participate in ownership, then you need to switch to shared_ptr. But from what you said, you just want to return an observer pointer that is going to be short-lived, so just use a raw pointer.

Herb Sutter has a nice article about this:

https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/


(Off-topic)

By the way, instead of having an out-parameter, you can directly return an int* instead of a bool. Return nullptr if the element was not found. So your function signature would be:

int* find_ref_in_list(int n, const std::list<int*>&l);
Nikos C.
  • 50,738
  • 9
  • 71
  • 96
0

So the question if it is possible to provide the outgoing reference to a unique_ptr or which is the right technique to encode this problem for unique_ptrs.

You invented your own term outgoing reference and using it you fool yourself. You do not have outgoing reference in your code, you simply return value thru reference. As you return value you cannot return std::unique_ptr without loosing ownership. To return reference you must use it as a return value, for example:

int*& find_ref_in_list(int n, const std::list<int*>&l, bool &found)

but now you have different issue, what to return when item is not found and function usage is difficult. There is no way to return reference through parameter as there is no such thing as reference to reference. So either return raw pointer by value or std::shared_ptr if you want to share ownership. Be careful with inventing your own terminology, you stop understanding what is really happening there.

Slava
  • 43,454
  • 1
  • 47
  • 90
  • What kind of reference would you return in case you can't find the element? – Achim Schön Aug 14 '19 at 14:16
  • I already mention that as a problem in my answer. You would have to throw an exception for example. I did not say it is a good solution I only see it as viable solution returning reference. But you better not doing that. And point is in your case you do not have **outgoing reference** you simply return **value** using reference as a mechanism. When you return value type pointer to `int` you are fine, when you try to return value of type `std::unique_ptr` you have obvious issue. – Slava Aug 14 '19 at 14:42
0

At the line

element = *iter; // causes error because assignment operator=

you are trying not to make reference element point to the *iter but to assign the content of the reference (std::unique_ptr in your case). BTW there is also an issue with const.

There are two possible workarounds:

  1. Pass element by pointer to unique_ptr:
    bool find_ref_in_list(int n, const std::list<std::unique_ptr<int>>&l, const std::unique_ptr<int>* element)
    {
        auto iter = std::find_if(l.begin(), l.end(), [n](const std::unique_ptr<int>& x)
        {
            return (*x == n) ? true : false;
        });

        element = &*iter;

        return true;
    }
  1. Use std::reference_wrapper which is a kind of assignable reference.
bool find_ref_in_list(int n, const std::list<std::unique_ptr<int>>&l, std::reference_wrapper<const std::unique_ptr<int>>& element)
{
    auto iter = std::find_if(l.begin(), l.end(), [n](const std::unique_ptr<int>& x)
    {
        return (*x == n) ? true : false;
    });

    element = std::ref(*iter);

    return true;
} 
Dmitry Gordon
  • 2,229
  • 12
  • 20
  • Your solution with `std::reference_wrapper` is what I have searched for, but I think it is not possible to declare a variable for this type. The following code is failing: `std::reference_wrapper> element;` – Achim Schön Aug 14 '19 at 14:12
0

You have to check the return value of the std::find_if statement. A return value iter == l.end() indicates the search failed and the function could return false. If the iterator is valid, the element should be reset to take a new ownership and the function could terminate by returning true;

bool find_ref_in_list(int n, const std::list<std::unique_ptr<int>>&l, std::unique_ptr<int>& element)
{
    auto iter = std::find_if(l.begin(), l.end(), [n](const std::unique_ptr<int>& x)
    {
        return (*x == n) ? true : false;
    });

    if (iter == l.end()){
      element.reset(nullptr);
      return false;
    }
    element.reset(iter->get());  
    return true;
}
seccpur
  • 4,996
  • 2
  • 13
  • 21
  • I'm sorry. When you have posted this as a comment above I was quite happy about this approach and therefor recommended you should post this as an answer. However thinking about this twice I see that this concept destroys the idea of an `unque_ptr`. Now the lifetime of our internal raw pointer is bound on the list and on the unique_ptr variable element. So when on of them goes out of scope the other becomes a dangling pointer. Sorry about that. – Achim Schön Aug 14 '19 at 16:48
  • @AchimSchön: You also `element.reset(nullptr);` if the search result is negative so the calling program can also verify that. – seccpur Aug 15 '19 at 01:59
  • the problem only occurs for successful searches. If the list l is for example globally defined, but element is on stack you will damage l as soon as element is destroyed. – Achim Schön Aug 15 '19 at 15:56