Automatische Referenzzählung in C++

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Migrieren von C++-Code zu mobilen Anwendungen vom Desktop aus


In C++ werden Objekte im Object Pascal-Stil (Objekte mit von TObject abgeleiteten Typen) aus dem Heap (mit new) zugewiesen und über einen Aufruf von delete freigegeben. In Appmethod wurde die Verwaltung der Lebenszeit dieser Objekte in mobilen Plattformen durch den Wechsel zur automatischen Referenzzählung (Automatic Reference Counting, ARC) geändert.

Wenn Sie mit ARC nicht vertraut sind, lesen Sie bitte die folgende Seite: Automatische Referenzzählung in mobilen Object Pascal-Compilern.

Grundlagen von ARC

Im Folgenden werden die Schlüsselkonzepte von ARC für Klassenzeiger im Object Pascal-Stil beschrieben:

  • Die Lebenszeit jedes Objekts wird mittels RefCount verwaltet (entspricht der Art und Weise, wie TInterfacedObject in Code ohne ARC verwaltet wird).
  • Jeder Zeiger fügt der Instanz einen Referenzzähler hinzu (das gilt für reguläre Zeiger, die als starke Zeiger behandelt werden).
  • Der Aufruf von delete für Klassenzeiger im Object Pascal-Stil dekrementiert lediglich den RefCount der Instanz und löscht den Zeiger.
  • Durch Zuweisen von null zu einem Klassenzeiger im Object Pascal-Stil wird der RefCount der Instanz dekrementiert und der Zeiger gelöscht.
  • Durch die Pseudo-Freigabe eines Klassenzeigers im Object Pascal-Stil wird der RefCount der Instanz dekrementiert und der Zeiger gelöscht.
  • Eine Funktion, die einen Klassenzeiger im Object Pascal-Stil zurückgibt, inkrementiert den RefCount.
  • Der Aufrufer einer Funktion, die einen Klassenzeiger im Object Pascal-Stil zurückgibt, dekrementiert am Ende des vollständigen Ausdrucks den RefCount des zurückgegebenen Zeigers (entspricht der Behandlung zurückgegebener temporärer Variablen in C++).

Beispiele für ARC

An den folgenden Codefragmenten werden die grundlegenden Unterschiede zwischen ARC-fähigem Code (mobil) und nicht-ARC-fähigem Code (Desktop) veranschaulicht. In den folgenden Codefragmenten wurde TObject verwendet, aber dasselbe Konzept gilt für alle Klassen im Object Pascal-Stil (wie TButton, TStringList, TForm etc.).

Verwenden Sie die Funktion GetObject, um darzustellen, wie zurückgegebene Klassenzeiger im Object Pascal-Stil behandelt werden :

C++:

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

Ohne ARC: Ohne ARC führt dieser Code zu einem Speicherleck, da die Instanz nicht gelöscht wurde.

ARC: Mit ARC wird der RefCount der Instanz inkrementiert, wenn p der von GetObject zurückgegebene Wert zugewiesen wird.

Wenn p außerhalb des Gültigkeitsbereichs liegt, wird die Eigenschaft RefCount dekrementiert und die Instanz freigegeben.

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

Ohne ARC: Die Instanz wird ordnungsgemäß bereinigt.

ARC: Mit ARC wird die Eigenschaft RefCount von p inkrementiert (wenn p ein zurückgegebener Klassenzeiger im Object Pascal-Stil zugewiesen wird)

und dekrementiert (beim Aufruf von delete). Durch Aufruf von "delete" wird auch der Wert von p auf null gesetzt. Dies ist erforderlich, damit der vom Compiler generierte

Code, der seinen RefCount dekrementiert, wenn der Gültigkeitsbereich verlassen wird, die Instanz nicht zweimal freigibt.

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

Ohne ARC: Dies führt auf dem Desktop zu einem Speicherleck. Durch Nullsetzen eines Zeigers wird nur die Variable geleert. Die Instanz wird nicht freigegeben.

ARC: Mit ARC wird die Instanz ordnungsgemäß bereinigt, da das Nullsetzen eines Klassenzeigers im Object Pascal-Stil identisch mit dem Aufrufen von delete ist:

der RefCount der Instanz wird dekrementiert.

void test4(bool flag) {
  TObject* outer;
  if (flag) {
	TObject* p1 = new TObject(); //RefCount von p1 ist 1
	// ...
	outer = p1; // inkrementiert RefCount
                    //(p1->RefCount=outer->RefCount=2)
	// ...
	delete p1; // (A) dekrementiert den RefCount (outer->RefCount = 1)
	// ...
  }
  // (B)
}

Ohne ARC: Bei (A) wird die Instanz freigegeben, und outer ist ein hängender/wilder Zeiger auf ein ungültiges Objekt.

ARC: Mit ARC wird die Instanz bei (A) nicht freigegeben, weil outer einen ausstehenden RefCount hat. Die Instanz wird bei (B) freigegeben,

wenn outer bereinigt ist.

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

Ohne ARC: Ohne ARC wird bei diesem Beispiel das zurückgegebene Objekt nicht bereinigt (bei Desktop-ARC hat TObject keine RefCount-Eigenschaft).

ARC: Mit ARC wird die zurückgegebene Instanz bereinigt.

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

Ohne ARC: Dieses Beispiel funktioniert bei aktiviertem ARC.

ARC: Mit aktiviertem ARC bereinigt der Destruktor des intelligenten Zeigers die zurückgegebene Instanz ordnungsgemäß.

__weak- und __unsafe-Zeiger

Zeiger auf Klassen im Object Pascal-Stil werden standardmäßig als starke Verweise behandelt. Sie können schwache und unsichere Zeiger auch mit __weak bzw. __unsafe deklarieren. Schwache Zeiger sollten zur Vermeidung von zirkulären (zyklischen) Verweisen verwendet werden. Beispielsweise könnte ein übergeordnetes Objekt starke Verweise auf alle untergeordneten Objekte haben, und diese können einen schwachen Zeiger auf das übergeordnete Objekt haben.

class A : public TObject {  //Übergeordnet
public:
	B* b;
	~A();
};
A::~A() {
}
class B : public TObject { //Untergeordnet
public:
	__weak A* a;
	~B();
};
B::~B() {
}

Schwache Zeiger werden von der Laufzeit verwaltet, sodass sie NULL sind, wenn das starke Objekt, auf das sie verweisen, freigegeben wird.

Unsichere Zeiger sind nicht verwaltete Rohzeiger. Bitte beachten Sie, dass __closures als schwache Zeiger verwaltet werden. Mit anderen Worten: Der Datenteil der closure wird gelöscht, wenn das zugrunde liegende starke Objekt freigegeben wird.

Bewährtes Vorgehen

  • Die goldene Regel, keine Rohzeiger für die Ressourcenverwaltung zu verwenden, gilt auch für ARC-fähige Klassenzeiger im Object Pascal-Stil. Verlassen Sie sich bei der Verwaltung von Ressourcen nicht auf den ARC-Mechanismus, sondern verwenden Sie stattdessen das Template auto_ptr.
  • Nullzeiger werden in einer Umgebung ohne ARC zu hängenden Zeigern. Verwenden Sie die Methode TObject.DisposeOf, um die Ausführung eines Destruktors zu erzwingen, wenn nicht klar ist, ob hängende Zeiger oder beabsichtigte Verweise auf eine Instanz vorhanden sind. Verwenden Sie aber, wenn möglich, einen RAII-Wrapper, und vermeiden Sie die Verwaltung von Ressourcen über Rohzeiger, mit oder ohne ARC-Unterstützung.

Siehe auch