Syntax guidelines for taking ownership and releasing objects in C++

喜欢而已 提交于 2019-12-03 07:13:37

I can't see why using smart pointers doesn't suffice. I can't think of anything else that I wouldn't categorize as code smell. Using smart pointers over raw pointers makes ownership and responsebilities perfectly clear:

  • auto_ptr/unique_ptr - single owner, ownership is transferred
  • shared_ptr - multiple owners, ownership may be transferred
  • scoped_ptr - single owner, ownership cannot be transferred
  • weak_ptr - observer (but shared_ptr may be created from weak_ptr)

I think that these suffice to clearly show the responsibilities, e.g.

void func(std::auto_ptr<Class> input) {...} // func() takes ownership of input
void func(std::shared_ptr<Class> input) {...} // func() and caller share ownership
std::auto_ptr<Class> func() {...} // caller takes ownership of returned value
std::shared_ptr<Class> func() {...} // func() and caller shares ownership of returned object
std::weak_ptr<Class> func() {...} // func() owns created object, but caller may observe it

As you mention, references are also great in this sense. Note if there's a need to free the pointers using some custom mechanism, shared_ptr and unique_ptr supports custom deleters. auto_ptr does not have this capability.

Note! If you are using C++ pre-11, you'll have to resort to boost::shared_ptr and boost:weak_ptr.

If I understand you, then Boost call_traits might be what you are looking for:

The example (copied from the docs goes):

template <class T>
struct contained
{
   // define our typedefs first, arrays are stored by value
   // so value_type is not the same as result_type:
   typedef typename boost::call_traits<T>::param_type       param_type;
   typedef typename boost::call_traits<T>::reference        reference;
   typedef typename boost::call_traits<T>::const_reference  const_reference;
   typedef T                                                value_type;
   typedef typename boost::call_traits<T>::value_type       result_type;

   // stored value:
   value_type v_;

   // constructors:
   contained() {}
   contained(param_type p) : v_(p){}
   // return byval:
   result_type value() { return v_; }
   // return by_ref:
   reference get() { return v_; }
   const_reference const_get()const { return v_; }
   // pass value:
   void call(param_type p){}

};

Not much clearer than param_type, reference_type and return_type to indicate what is meant.

I just use this syntax for parameters where needed:

example constructor declaration:

t_array(const t_ownership_policy::t_take& policy, THESpecialType* const arg);

In use at the callsite:

t_array array(t_ownership_policy::Take, THESpecialTypeCreate(...));

Where t_ownership_policy::t_take is just a dummy overload disambiguator typename.

In this system, there are multiple policies, each with separate types. I favored unique types per policy because a typed enumeration (for example) does not support initialization as easily, and it's too easy to pass an unsupported policy to a function or constructor. 'Polymorphic' policies can reduce symbol count, but it's a pain because it pushes error detection to runtime.

For 'returning':

void func(t_container<t_type>& outValue);

Where t_container is whichever pointer container type you choose. Then the container type already implements the necessary boilerplate. This container may be something like a shared_ptr, or some specialization you have written.

and for more elaborate types, i'll often use syntax like so:

void func(t_special_container& outValue) {
  ...
  outValue.take(ptr);
  - or -
  outValue.copy(ptr);
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!