Can smart pointers in Objective-C++ completely replace ARC?

Viewed 83

I am a beginner in Cocoa framework and have a question:

Suppose I don't use "naked" NS-pointers in my application, is ARC superfluous for me then ? :-)

In other words, does the following implementation cover all of ARC's concerns ? :-)

Thank you !

First (result accumulation of "alloc..", "new..", "copy.." or "mutableCopy.."):

#pragma once

// In-Ref(NOP), Out-Ref(--)
template<typename T>
class NSChargePtr
{
  NSChargePtr(const NSChargePtr&) = delete;

protected:
  T* m_object{};

public:
  NSChargePtr(T* p = __nullptr)
    : m_object(p)
  {
  }
  virtual ~NSChargePtr()
  {
    [m_object release];
  }
  operator T*()
  {
    return m_object;
  }
  operator bool()
  {
    return __nullptr != m_object;
  }
  void operator=(T* p)
  {
    reset(p);
  }
  T* get()
  {
    return m_object;
  }
  virtual void reset(T* p = __nullptr)
  {
    [m_object release];
    m_object = p;
  }
}; 

...and second (local keeping, transfer):

#pragma once
#include "NSChargePtr.h"

// In-Ref(++), Out-Ref(--)
template<typename T>
class NSPassPtr : public NSChargePtr<T>
{
public:
  NSPassPtr(T* p = __nullptr)
    : NSChargePtr<T>(p)
  {
    [NSChargePtr<T>::m_object retain];
  }
  NSPassPtr(const NSPassPtr& other)
    : NSChargePtr<T>(other.m_object)
  {
  }
  virtual void reset(T* p = __nullptr) override
  {
    NSChargePtr<T>::reset(p);
    [NSChargePtr<T>::m_object retain];
  }
  void operator=(T* p)
  {
    NSChargePtr<T>::operator=(p);
  }
};
1 Answers

In short: You can do it, but it will probably not be as painless as "real" ARC, and it's not clear that this approach provides any improvement over ARC. Why do you even want to do this?

Some potential issues that immediately stick out to me:

  • Your distinction between NSPassPtr and NSChargePtr is already a major source of potential confusion. You need to know exactly which one to use in which case, depending on whether a method returns an autoreleased value or a retained value. You also can't cleanly re-assign the result of an autoreleasing expression to a variable that was initialised with the result of a retaining expression.
{
   NSChargePtr<MyClass> obj = [[MyClass alloc] init];
   // …
   obj = [foo bar];
   // …
} // over-release of result of [foo bar];
  • Thread safety. I don't see any evidence of defending against race conditions in your implementation. ARC has well-defined behaviour in the context of multithreading.
  • Performance & compiler optimisations. The compiler is aware of ARC and can elide retain & release calls where it can prove they aren't necessary. It can't do that with this implementation. ARC's objc_retain/objc_release probably are faster than sending retain/release messages too.
  • Implicit conversion operator overloads (operator T*(), operator bool, non-explicit constructors such as NSChargePtr(T* p = __nullptr)) tend to be a source of errors.

I should point out that Objective-C++ is compatible with ARC and that ARC references are well-behaved in the context of C++ object construction/destruction. So usually if you're really struggling to make some piece of code work with ARC, it's better to move that to a separate code file that has ARC disabled, but to leave ARC enabled for the rest of the project.

Finally:

NSPassPtr(const NSPassPtr& other)
    : NSChargePtr<T>(other.m_object)
  {
  }

This looks to me like it's missing a retain?

Related