Frage

Welche Alternativen habe ich in C++ zum Offenlegen einer Sammlung im Hinblick auf Leistung und Datenintegrität?

Mein Problem besteht darin, dass ich dem Anrufer eine interne Datenliste zurückgeben möchte, aber keine Kopie erstellen möchte.Dann muss ich entweder einen Verweis auf die Liste oder einen Zeiger auf die Liste zurückgeben.Allerdings bin ich nicht so begeistert davon, den Anrufer die Daten ändern zu lassen, ich möchte ihn nur die Daten lesen lassen.

  • Muss ich mich zwischen Leistung und Datenintegrität entscheiden?
  • Wenn ja, ist es im Allgemeinen besser, in eine Richtung zu gehen, oder ist dies auf den Einzelfall beschränkt?
  • Gibt es andere Alternativen?
War es hilfreich?

Lösung

RichQs Antwort ist eine sinnvolle Technik, wenn Sie ein Array, einen Vektor usw. verwenden.

Wenn Sie eine Sammlung verwenden, die nicht durch Ordnungswerte indiziert ist ...oder denke du Möglicherweise muss es sein irgendwann in naher Zukunft...Dann sollten Sie vielleicht darüber nachdenken, Ihre eigenen Iteratortypen und die damit verbundenen zu veröffentlichen begin()/end() Methoden:

class Blah
{
public:
   typedef std::vector<mydata> mydata_collection;
   typedef myDataCollection::const_iterator mydata_const_iterator;

   // ...

   mydata_const_iterator data_begin() const 
      { return myPreciousData.begin(); }
   mydata_const_iterator data_end() const 
      { return myPreciousData.end(); }

private:
   mydata_collection  myPreciousData;
};

...die Sie dann wie gewohnt verwenden können:

Blah blah;
for (Blah::mydata_const_iterator itr = blah.data_begin();
   itr != blah.data_end();
   ++itr)
{
   // ...
}

Andere Tipps

Oft möchte der Aufrufer Zugriff haben, nur um die Sammlung zu durchlaufen.Nehmen Sie eine Seite aus Rubys Buch und machen Sie die Iteration zu einem privaten Aspekt Ihrer Klasse.

#include <algorithm>
#include <boost/function.hpp>

class Blah
{
  public:
     void for_each_data(const std::function<void(const mydata&)>& f) const
     {
         std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
     }

  private:
     typedef std::vector<mydata> mydata_collection;
     mydata_collection  myPreciousData;
};

Mit diesem Ansatz geben Sie nichts über Ihr Inneres preis, d. h.dass du sogar haben eine Sammlung.

Vielleicht so etwas?

const std::vector<mydata>& getData()
{
  return _myPrivateData;
}

Der Vorteil hierbei ist, dass es sehr, sehr einfach und so sicher ist wie in C++.Sie können dies wirken, wie RobQ vorschlägt, aber Sie können nichts tun, was jemanden davon abhalten würde, wenn Sie nicht kopieren.Hier müsste man verwenden const_cast, was ziemlich leicht zu erkennen ist, wenn Sie danach suchen.

Alternativ könnten Iteratoren fast das Gleiche bewirken, aber es ist komplizierter.Der einzige zusätzliche Vorteil der Verwendung von Iteratoren hier (der mir einfällt) besteht darin, dass Sie eine bessere Kapselung erreichen können.

Die Verwendung einer konstanten Referenz oder eines gemeinsamen Zeigers hilft nur, wenn sich der Inhalt der zugrunde liegenden Sammlung im Laufe der Zeit nicht ändert.

Denken Sie über Ihr Design nach.Muss der Anrufer wirklich das interne Array sehen?Können Sie den Code so umstrukturieren, dass der Aufrufer dem Objekt mitteilt, was mit dem Array zu tun ist?Wenn der Aufrufer beispielsweise beabsichtigt, das Array zu durchsuchen, könnte das Eigentümerobjekt dies tun?

Sie könnten der Funktion einen Verweis auf den Ergebnisvektor übergeben.Bei einigen Compilern kann dies zu geringfügig schnellerem Code führen.

Ich würde empfehlen, zuerst eine Neugestaltung zu versuchen, dann eine saubere Lösung zu wählen und drittens die Leistung zu optimieren (falls erforderlich).

Ein Vorteil der Lösungen von @Shog9 und @RichQ besteht darin, dass sie den Client von der Sammlungsimplementierung entkoppeln.

Wenn Sie sich entscheiden, Ihren Sammlungstyp in einen anderen zu ändern, funktionieren Ihre Kunden weiterhin.

Was Sie wollen, ist schreibgeschützter Zugriff, ohne den gesamten Datenblob zu kopieren.Sie haben mehrere Möglichkeiten.

Erstens könnten Sie einfach eine konstante Referenz auf Ihren Datencontainer zurückgeben, wie oben vorgeschlagen:

const std::vector<T>& getData() { return mData; }

Dies hat den Nachteil der Konkretheit:Sie können die Art und Weise, wie Sie die Daten intern speichern, nicht ändern, ohne die Schnittstelle Ihrer Klasse zu ändern.

Zweitens können Sie konstante Zeiger auf die tatsächlichen Daten zurückgeben:

const T* getDataAt(size_t index)
{
   return &mData[index];
}

Das ist etwas schöner, erfordert aber auch, dass Sie einen getNumItems-Aufruf bereitstellen und vor Indizes schützen, die außerhalb der Grenzen liegen.Außerdem geht die Konstanz Ihrer Zeiger leicht verloren, und Ihre Daten sind jetzt schreib- und lesbar.

Eine andere Möglichkeit besteht darin, ein Paar Iteratoren bereitzustellen, was etwas komplexer ist.Dies hat die gleichen Vorteile wie Zeiger, außerdem ist (nicht unbedingt) die Bereitstellung eines getNumItems-Aufrufs erforderlich, und es ist erheblich mehr Arbeit erforderlich, die Iteratoren von ihrer Konstanz zu befreien.

Der wahrscheinlich einfachste Weg, dies zu bewältigen, ist die Verwendung eines Boost-Bereichs:

typedef vector<T>::const_iterator range_iterator_type;
boost::iterator_range< range_iterator_type >& getDataRange()
{
    return boost::iterator_range(mData.begin(), mData.end());
}

Dies hat den Vorteil, dass Bereiche zusammensetzbar, filterbar usw. sind, wie Sie auf der Seite sehen können Webseite.

Die Verwendung von const ist eine vernünftige Wahl.Möglicherweise möchten Sie auch die Boost-C++-Bibliothek für ihre Shared-Pointer-Implementierung ausprobieren.Es bietet die Vorteile von Zeigern, d. h.Möglicherweise müssen Sie einen gemeinsam genutzten Zeiger auf „null“ zurückgeben, was eine Referenz nicht zulassen würde.

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

In Ihrem Fall würden Sie den Typ des gemeinsam genutzten Zeigers auf const festlegen, um Schreibvorgänge zu verhindern.

Wenn Sie eine haben std::list von einfachen alten Daten (was .NET „Werttypen“ nennen würde), dann ist die Rückgabe eines konstanten Verweises auf diese Liste in Ordnung (böse Dinge wie ignorieren). const_cast)

Wenn Sie eine haben std::list von Zeigern (bzw boost::shared_ptr's), dann hindert Sie das nur daran, die Sammlung zu ändern, nicht die Elemente In die Sammlung.Mein C++ ist zu eingerostet, um Ihnen an dieser Stelle die Antwort darauf sagen zu können :-(

Ich schlage vor, Rückrufe im Sinne von zu verwenden EnumChildWindows.Sie müssen Mittel finden, um zu verhindern, dass der Benutzer Ihre Daten ändert.Vielleicht verwenden Sie a const Zeiger/Referenz.

Andererseits könnten Sie eine Kopie jedes Elements an die Rückruffunktion übergeben und die Kopie jedes Mal überschreiben.(Sie möchten keine Kopie Ihrer gesamten Sammlung erstellen.Ich schlage nur vor, jeweils ein Element einzeln zu kopieren.Das sollte nicht viel Zeit/Speicher in Anspruch nehmen.

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}

In den folgenden beiden Artikeln werden einige der mit der Kapselung von Containerklassen verbundenen Probleme und die Notwendigkeit dafür näher erläutert.Obwohl sie keine vollständig funktionierende Lösung bieten, führen sie im Wesentlichen zu demselben Ansatz wie Shog9.

Teil 1: Einkapselung und Vampire
Teil 2 (zum Lesen ist jetzt eine kostenlose Registrierung erforderlich): Zugunglücksbeobachtung
von Kevlin Henney

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top