Automatische Referenzzählung in C++

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Gesichtspunkte für geräteübergreifende C++-Anwendungen


In C++ werden Objekte im Delphi-Stil (Objekte mit von TObject abgeleiteten Typen) aus dem Heap (mit new) zugewiesen und über einen Aufruf von delete freigegeben. In RAD Studio wurde die Verwaltung der Lebenszeit dieser Objekte auf 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 Delphi-Compilern.

Grundlagen von ARC

Im Folgenden werden die Schlüsselkonzepte von ARC für Klassenzeiger im Delphi-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 Delphi-Stil dekrementiert lediglich den RefCount der Instanz und löscht den Zeiger.
  • Durch Zuweisen von null zu einem Klassenzeiger im Delphi-Stil wird der RefCount der Instanz dekrementiert und der Zeiger gelöscht.
  • Durch die Pseudo-Freigabe eines Klassenzeigers im Delphi-Stil wird der RefCount der Instanz dekrementiert und der Zeiger gelöscht.
  • Eine Funktion, die einen Klassenzeiger im Delphi-Stil zurückgibt, inkrementiert den RefCount.
  • Der Aufrufer einer Funktion, die einen Klassenzeiger im Delphi-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

Anhand der folgenden Codefragmente 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 Delphi-Stil (wie TButton, TStringList, TForm etc.).

Verwenden Sie die Funktion GetObject, um darzustellen, wie zurückgegebene Klassenzeiger im Delphi-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 Delphi-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 Delphi-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(); //p1's RefCount is 1
	// ...
	outer = p1; // increments RefCount
                    //(p1->RefCount=outer->RefCount=2)
	// ...
	delete p1; // (A) decrements the 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 Delphi-Stil werden standardmäßig als starke Referenzen behandelt. Sie können schwache und unsichere Zeiger auch mit __weak bzw. __unsafe deklarieren. Schwache Zeiger sollten zur Vermeidung von zirkulären (zyklischen) Referenzen verwendet werden. Beispielsweise könnte ein übergeordnetes Objekt starke Referenzen auf alle untergeordneten Objekte haben, und diese können einen schwachen Zeiger auf das übergeordnete Objekt haben.

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

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

Schwache Zeiger werden vom Laufzeitmodul 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 des 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 Delphi-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