Automatic Reference Counting in C++

From RAD Studio
Jump to: navigation, search

Go Up to C++ Considerations for Multi-Device Applications


In C++, Delphi-style objects (objects of types derived from TObject) are allocated off the heap (via new) and freed via a call to delete. RAD Studio changed how the lifetime of these objects is managed in mobile platforms by switching to Automatic Reference Counting (ARC).

If you are not familiar with ARC, please read the following page: Automatic Reference Counting in Delphi Mobile Compilers.

ARC Basics

The key concepts of ARC for Delphi-style class pointers are described below:

  • The lifetime of each object is managed via a RefCount (similar to the way TInterfacedObject is managed on non-ARC code)
  • Each pointer adds a Reference Count to the instance (this is for regular pointers, which are treated as strong pointers)
  • Invoking delete on a Delphi-style class pointer merely decrements the RefCount of the instance and clears the pointer
  • Assigning zero to a Delphi-style class pointer decrements the RefCount of the instance and clears the pointer
  • Pseudo-destroying a Delphi-style class pointer decrements the RefCount of the instance and clears the pointer
  • A function returning a Delphi-style class pointer increments the RefCount
  • The caller of a function returning a Delphi-style class pointer decrements the RefCount of the returned pointer at the end of the full-expression (the way C++ handles returned temporaries).

ARC Examples

The code snippets below highlight the basic differences between ARC-enabled code (Mobile) and non-ARC-enabled code (Desktop). The snippets below use TObject but the same concept applies to every Delphi-style class (such as TButton, TStringList, TForm, etc).

To illustrate how returned Delphi-style class pointers are handled, use the GetObject function :

C++:

TObject* GetObject() {
	return new TObject();
}
Code Example Notes
void test1() {
  TObject* p = GetObject();
  ShowMessage(System::Sysutils::IntToStr(p->RefCount));
}

Non-ARC: In non-ARC this code results in a leak as the instance was not deleted.

ARC: With ARC, when p is assigned the value returned by GetObject, the RefCount of an instance is incremented.

When p goes out of scope, the RefCount property is decremented and the instance is freed.

void test2() {
  TObject* p = GetObject();
  delete p;
}

Non-ARC: The instance is properly cleaned up.

ARC: With ARC, the RefCount property of p is incremented (when assigning to p a returned Delphi-style class pointer)

and decremented (when calling delete). The call to delete will also zero the value of p. This is required so that the compiler-generated

code that decrements its RefCount when it goes out of scope does not double free the instance.

void test3() {
  TObject* p = GetObject();
  p = 0;
}

Non-ARC: This results in a leak on the desktop. Zeroing a pointer simply clears the variable. It does not destroy the instance.

ARC: With ARC, the instance is properly cleaned up with ARC as zeroing a Delphi-style class pointer is identical to invoking delete:

it decrements the RefCount of the instance.

void test4(bool flag) {
  TObject* outer;
  if (flag) {
	TObject* p1 = new TObject(); //p1's RefCount is 1
	// ...
	outer = p1; // increments RefCount
                    //(p1->RefCount=outer->RefCount=2)
	// ...
	delete p1; // (A) decrements the RefCount (outer->RefCount = 1)
	// ...
  }
  // (B)
}

Non-ARC: At (A) the instance is destroyed and outer is a dangling/wild pointer to an invalid object.

ARC: With ARC, the instance is not destroyed at (A) because outer has an outstanding RefCount.The instance is destroyed at (B)

when outer is cleaned up.

int test5() {
  return GetObject()->RefCount;
}

Non-ARC: Without ARC, this example leaks the returned object (on Desktop ARC, TObject has no RefCount property).

ARC: With ARC, the returned instance is cleaned up.

int test6() {
  return std::auto_ptr<TObject>(GetObject())->RefCount;
}

Non-ARC: This example does not leak with ARC enabled.

ARC: With ARC enabled, the destructor of the smart pointer properly cleans up the returned instance.

__weak and __unsafe pointers

Pointers to Delphi-style classes are treated as strong references by default. You can also declare weak and unsafe pointers using __weak and __unsafe respectively. Weak pointers should be used to avoid circular (cyclic) references. For example, a Parent object may have strong references to all of its Children, and the latter may have a weak pointer to the Parent.

class A : public TObject {  //Parent
public:
	B* b;
	~A();
};
A::~A() {
}

class B : public TObject { //Child
public:
	__weak A* a;
	~B();
};
B::~B() {
}

Weak pointers are managed by the runtime so that they are NULL when the strong object they refer to is destroyed.

Unsafe pointers are unmanaged raw pointers. It is important to note that __closures are managed as weak pointers. In other words, the data portion of the closure is cleared when the underlying strong object is destroyed.

Best practice

  • The golden rule of not using raw pointers for resource management is true even for ARC-enabled Delphi-class style pointers. Do not rely on the ARC mechanism to manage a resource, use the auto_ptr template instead.
  • Zero pointers would turn into dangling pointers in a non-ARC environment. You may use the TObject.DisposeOf method to force the execution of a destructor when it is not obvious when there are dangling pointers or intentional references to an instance. But whenever possible use a RAII wrapper and avoid managing resources via raw pointers, with or without ARC support.

See Also