Ruggero Turra
Ruggero Turra

Reputation: 17670

smart pointer the element of a std::pair

I have a function which return a std::pair<objectA*, objectB*>. The documentation of the function says that it is my responsibility to deallocate the two elements. Now I am simply doing:

{
  std::pair<objectA*, objectB*> the_pair;

  ...

  if (condition) {
     delete the_pair.first;
     delete the_pair.second;
     return;
  }

  ...

  delete the_pair.first;
  delete the_pair.second;
}

how can I use smart pointer to automate the deletion of the two elements when the_pair goes out of scope?

Upvotes: 2

Views: 3848

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

A std::pair<std::unique_ptr<A>,std::unique_ptr<B>> is the obvious solution. But we want it to be clean at the point of use. So:

template<class...Ts, template<class...>class tup, class... Us, size_t...Is>
tup<Ts...> tuple_cast(std::index_sequence<Is...>,tup<Us...>&&u){
  return tup<Ts...>{static_cast<Ts>(std::get<Is>(std::move(u)))...};
}
template<class...Ts, template<class...>class tup, class... Us, size_t...Is>
tup<Ts...> tuple_cast(std::index_sequence<Is...>,tup<Us...>const&u){
  return tup<Ts...>(static_cast<Ts>(std::get<Is>(u))...);
}
template<class...Ts, template<class...>class tup, class... Us>
tup<Ts...> tuple_cast(tup<Us...>&&u){
  static_assert( sizeof...(Ts)==sizeof...(Us), "must map one type to one type" );
  return tuple_cast<Ts...>(std::index_sequence_for<Us...>{}, std::move(u));
}
template<class...Ts, template<class...>class Tup, class... Us>
Tup<Ts...> tuple_cast(Tup<Us...>const&u){
  static_assert( sizeof...(Ts)==sizeof...(Us), "must map one type to one type" );
  return tuple_cast<Ts...>(std::index_sequence_for<Us...>{}, u);
}

now auto x = tuple_cast<std::unique_ptr<A>,std::unique_ptr<B>>( func() ) is a way to turn the pair of pointers to a pair of unique pointers.

We can do better.

template<class A>
struct as_unique{using type=A;};
template<class A>
struct as_unique<A*>{using type=std::unique_ptr<A>;};
template<class A>
using as_unique_t=typename as_unique<A>::type;

template<class...Ts, template<class...>class tup>
tup<as_unique_t<Ts>...> take_ownership(tup<Ts...>&& t){
  return tuple_cast<as_unique_t<Ts>...>(std::move(t));
}

and if the typos are fixed, we get:

auto p = take_ownership( func() );

which looks cleaner at point of use.2 This takes any pointer values T* in your std::pair or std::tuple, converts them to std::unique_ptr<T>s, generates the corresponding type for output, then casts each field using static_cast to make the conversion explicit.

live example

The above uses C++14 index sequences. They can be written in C++11 in a dozen lines or so, so I don't feel bad about it.

Upvotes: 7

juanchopanza
juanchopanza

Reputation: 227390

If I understand correctly, it is the responsibility of the caller to delete the pointers. In that case, you could create a unique_ptr managing each element of the pair:

{ // some scope

  std::pair<objectA*, objectB*> the_pair = the_function();
  std::unique_ptr<objectA> pfirst(the_pair.first);
  std::unique_ptr<objectB> psecond(the_pair.second);

} // pointers get deleted

Alternatively, you can write your own scope guard:

struct pointer_pair_guard
{
  pointer_pair_guard(std::pair<objectA*, objectB*>& p) : p_(p) {}
  ~pointer_pair_guard() 
  { 
    delete p_.first;
    delete p_.second;
  }
  pointer_pair_guard(const pointer_pair_guard&) = delete;
  pointer_pair_guard& operator=(const pointer_pair_guard&) = delete;
  private:
    std::pair<objectA*, objectB*>& p_;
};

then

{ // some scope

  std::pair<objectA*, objectB*> the_pair = the_function();
  pointer_pair_guard gd(the_pair);

} // pointers get deleted

Upvotes: 8

Bathsheba
Bathsheba

Reputation: 234685

I'd be tempted to stub the function and use the stub.

std::pair<
    std::shared_ptr<objectA>,
    std::shared_ptr<objectB>
> nice_foo(...)
{
    std::pair<objectA*, objectB*> temp = bad_foo(...);    
    return std::pair<
        std::shared_ptr<objectA>,
        std::shared_ptr<objectB>
    >(std::shared_ptr<objectA>(temp.first), std::shared_ptr<objectB>(temp.second));
}

In your code, instead of calling bad_foo, call nice_foo. The returned pair owns the memory so you don't need to worry about deleting it.

Upvotes: 8

Related Questions