RAII kontrollierte Flags in Qt

Flags sind ein beliebtes Werkzeug, um das Ausführungsverhalten eines Algorithmus zu beeinflussen. Das Steuern der Wertbelegung von Flags ist jedoch fehleranfällig. Durch Anwendung des in C++ bewerten RAII1-Konzepts kann die Fehleranfälligkeit reduziert werden.

RAII bedeutet, dass das Instanziieren einer Klasse gleichzeitig die Klassenressourcen initialisiert. Das Zerstören der Instanz gibt die Klassenressourcen frei. Dieser Umstand wird z.B. durch SmartPointer2 ausgenutzt.

Beispielsweise ist eine Klasse Foo implementiert worden in der es Qt-bedingt zu verschachtelten Aufrufen von Slots kommt. Dem Entwickler ist klar, dass die verschachtelten Aufrufe ein Nebeneffekt von Signal/Slot-Verknüpfungen sind.

Die verschachtelten Aufrufe müssen verhindert werden. Ohne mit objektorientierten Ansätzen auszuholen kann die Ausführung mit Hilfe von Flags verhindert werden. Foo sei wie folgt implementiert:


class Foo: public QObject
{
  Q_OBJECT
public:
  Foo(QObject *parent = 0): QObject(parent), m_silent(false), m_value(0)
  {
    this->connect(this, SIGNAL(valueChanged()), SLOT(onValueChanged()));
  }

  void setValue(int value)
  {
    if(m_value == value) {
      return;
    }
    m_value = value;

    if(!m_silent) {
      emit valueChanged();
    }
  }

signals:
  void valueChanged();

private slots:
  void onValueChanged()
  {
    m_silent = true;
    this->setValue(m_value + 1);
    if(!this->complexAlgorithm()) {
      return;
    }
    m_silent = false;
  }

private:
  bool complexAlgorithm()
  {
    //Evil code
    return false;
  }

private:
  bool m_silent;
  int m_value;
};

Die Methode void setValue(int) setzt das Attribut m_value und löst, sofern das Flag m_silent == false, das Signal valueChanged() aus. Der Slot onValueChanged() behandelt die Veränderung von m_value (Verknüpfung im Konstruktor). Die Behandlung setzt das Flag m_silent = true und ruft erneut die Methode void setValue(int) mit dem Wert m_value + 1 auf. Danach wird die Methode bool complexAlgorithm() aufgerufen. Liefert diese ein false als Ergebnis wird der Slot verlassen. Anderenfalls wird m_silent = false gesetzt. Ohne das Flag m_silent würde void setValue(int) immer wieder die Ausführung des Slots onValueChanged() zur Folge haben.

Die Implementierung hat einen gravierenden Fehler. bool complexAlgorithm() liefert fingierter Weise immer false. Das Flag m_silent wird nie wieder auf true gesetzt weil der Slot nicht mehr ausgeführt wird. Dadurch löst void setValue(int) im normalen Programmablauf nie wieder das Signal valueChanged() aus.

Eine Lösung zu diesem Problem könnte wie folgt aussehen:


void onValueChanged()
{
  m_silent = true;
  this->setValue(m_value + 1);
  if(!this->complexAlgorithm()) {
    m_silent = false;
    return;
  }
  m_silent = false;
}

Das ist eine fehleranfällige Lösung. Sobald der Quellcode in void onValueChanged() komplexer wird kann das Zurücksetzen des Flags vergessen werden.

Lösung

Abhilfe schafft die Qt-Klasse QScopedValueRollback. Die Klasse kontrolliert den Wert einer Variablen beliebigen Typs auf RAII-Basis. Neben boolschen Flags können also auch andere Variablentypen gesteuert werden.

Eine Lösung mit QScopedValueRollback:


void onValueChanged()
{
  QScopedValueRollback<bool> silentRollback(m_silent);
  m_silent = true;
  this->setValue(m_value + 1);
  if(!this->complexAlgorithm()) {
    return;
  }
}

Der Konstruktor von QScopedValueRollback speichert den aktuellen Wert von m_silent. Der Destruktor stellt den ursprünglichen Wert wieder her. Da silentRollback als Stack-Objekt angelegt wurde erfolgt immer eine Zerstörung bei Verlassen des Methoden-Blocks.

Mit QScopedValueRollback::commit() kann der Wert von silentRollback aktualisiert werden, sodass beim Verlassen der Wert erhalten bleibt.

1 Ressourcenbelegung ist Initialisierung

2 SmartPointer

←zurück

© 2012 – 2024 HicknHack Software GmbH | Impressum | Datenschutzerklärung