Boost.Flyweight ist eine Bibliothek, die es einfach macht, das gleichnamige Entwurfsmuster einzusetzen – auf Deutsch Fliegengewicht genannt. Dabei geht es darum, Speicherplatz zu sparen, wenn sich viele Objekte Daten teilen. Anstatt die gleichen Daten mehrfach in Objekten zu speichern, sorgt das Entwurfsmuster Flyweight dafür, dass die geteilten Daten nur einmal im Speicher gehalten werden und alle Objekte auf diese Daten verweisen. Während man dieses Entwurfsmuster auch selbst zum Beispiel über Zeiger umsetzen könnte, geht dies mit einer Bibliothek wie Boost.Flyweight einfacher.
#include <string>
#include <vector>
struct person
{
int id_;
std::string city_;
};
int main()
{
std::vector<person> persons;
for (int i = 0; i < 100000; ++i)
persons.push_back({i, "Berlin"});
}
Im Beispiel 66.1 werden hunderttausend Objekte vom Typ person
erstellt. person
besitzt zwei Eigenschaften: Über id_ werden Personen identifiziert, und city_ speichert die Stadt, in der Personen leben. In diesem Beispiel leben alle Personen in Berlin. Deswegen wird die Eigenschaft city_ aller hunderttausend Objekte auf „Berlin“ gesetzt. Im Beispiel werden folglich hunderttausend Strings verwendet, die alle den gleichen Wert speichern. Mit Boost.Flyweight kann anstatt hunderttausend Strings ein String verwendet und der Speicherbedarf drastisch reduziert werden.
#include <boost/flyweight.hpp>
#include <string>
#include <vector>
#include <utility>
using namespace boost::flyweights;
struct person
{
int id_;
flyweight<std::string> city_;
person(int id, std::string city) : id_{id}, city_{std::move(city)} {}
};
int main()
{
std::vector<person> persons;
for (int i = 0; i < 100000; ++i)
persons.push_back({i, "Berlin"});
}
Um Boost.Flyweight zu verwenden, muss wie im Beispiel 66.2 die Headerdatei boost/flyweight.hpp
eingebunden werden. Boost.Flyweight bietet weitere Headerdateien an, die jedoch nur eingebunden werden müssen, wenn detaillierte Einstellungen in der Bibliothek geändert werden sollen.
Alle von Boost.Flyweight definierten Klassen und Funktionen befinden sich im Namensraum boost::flyweights
. Im Beispiel 66.2 wird ausschließlich auf die Klasse boost::flyweights::flyweight
zugegriffen, die die wichtigste Klasse dieser Bibliothek darstellt. Die Eigenschaft city_ hat nun nicht mehr den Typ std::string
, sondern den Typ flyweight<std::string>
. Diese Änderung reicht aus, um das Fliegengewicht-Entwurfsmuster einzusetzen und den Speicherbedarf des Programms zu verringern.
boost::flyweights::flyweight
mehrfach verwenden#include <boost/flyweight.hpp>
#include <string>
#include <vector>
#include <utility>
using namespace boost::flyweights;
struct person
{
int id_;
flyweight<std::string> city_;
flyweight<std::string> country_;
person(int id, std::string city, std::string country)
: id_{id}, city_{std::move(city)}, country_{std::move(country)} {}
};
int main()
{
std::vector<person> persons;
for (int i = 0; i < 100000; ++i)
persons.push_back({i, "Berlin", "Germany"});
}
Im Beispiel 66.3 wurde der Klasse person
eine zweite Eigenschaft country_ hinzugefügt. Diese Eigenschaft soll speichern, in welchem Land Personen leben. Da alle Personen in Berlin leben, leben sie folglich auch im gleichen Land. Deswegen wird boost::flyweights::flyweight
auch zur Definition der Eigenschaft country_ verwendet.
Boost.Flyweight verwendet intern einen Container, in dem Objekte gespeichert werden. Er stellt sicher, dass nicht mehrere Objekte mit gleichen Werten existieren. Boost.Flyweight verwendet dazu standardmäßig einen Hash-Container – also einen Container ähnlich wie std::unordered_set
. Für unterschiedliche Typen werden unterschiedliche Hash-Container verwendet. Da im Beispiel 66.3 beide Eigenschaften city_ und country_ Strings sind, wird nur ein Container verwendet. Das ist in diesem Beispiel kein Problem, da der Container nur zwei Strings mit „Berlin“ und „Germany“ speichert. Würden viele unterschiedliche Städte und Länder gespeichert werden müssen, wäre es besser, wenn Städte in einem und Länder in einem anderen Container gespeichert werden würden.
boost::flyweights::flyweight
mit Tags mehrfach verwenden#include <boost/flyweight.hpp>
#include <string>
#include <vector>
#include <utility>
using namespace boost::flyweights;
struct city {};
struct country {};
struct person
{
int id_;
flyweight<std::string, tag<city>> city_;
flyweight<std::string, tag<country>> country_;
person(int id, std::string city, std::string country)
: id_{id}, city_{std::move(city)}, country_{std::move(country)} {}
};
int main()
{
std::vector<person> persons;
for (int i = 0; i < 100000; ++i)
persons.push_back({i, "Berlin", "Germany"});
}
Im Beispiel 66.4 wird ein zweiter Template-Parameter an boost::flyweights::flyweight
übergeben. Es handelt sich hierbei um einen Tag. Tags sind beliebige Typen, die ausschließlich dazu dienen, die Typen, auf denen city_ und country_ basieren, unterscheidbar zu machen. So sind im Beispiel 66.4 zwei leere Strukturen city
und country
definiert worden, die als Tags verwendet werden. Es hätten aber genauso gut zum Beispiel int
und bool
verwendet werden können.
Die Tags führen dazu, dass city_ und country_ auf unterschiedlichen Typen basieren. Somit werden zwei Hash-Container von Boost.Flyweight verwendet – der eine speichert Städte, der andere Länder.
boost::flyweights::flyweight
#include <boost/flyweight.hpp>
#include <boost/flyweight/set_factory.hpp>
#include <boost/flyweight/no_locking.hpp>
#include <boost/flyweight/no_tracking.hpp>
#include <string>
#include <vector>
#include <utility>
using namespace boost::flyweights;
struct person
{
int id_;
flyweight<std::string, set_factory<>, no_locking, no_tracking> city_;
person(int id, std::string city) : id_{id}, city_{std::move(city)} {}
};
int main()
{
std::vector<person> persons;
for (int i = 0; i < 100000; ++i)
persons.push_back({i, "Berlin"});
}
Außer einem Tag können boost::flyweights::flyweight
auch andere Template-Parameter übergeben werden. Im Beispiel 66.5 sind dies boost::flyweights::set_factory
, boost::flyweights::no_locking
und boost::flyweights::no_tracking
. Beachten Sie, dass zur Verwendung dieser Klassen zusätzliche Headerdateien eingebunden werden müssen.
boost::flyweights::set_factory
gibt an, dass Boost.Flyweight keinen Hash-Container, sondern einen sortierten Container ähnlich wie std::set
verwenden soll. Über boost::flyweights::no_locking
wird die Unterstützung für Multithreading deaktiviert, die standardmäßig verwendet wird. boost::flyweights::no_tracking
wiederum gibt an, dass Boost.Flyweight nicht erfassen soll, ob Objekte, die in den intern verwendeten Containern gespeichert sind, verwendet werden. Werden Objekte nicht mehr verwendet, erkennt Boost.Flyweight das standardmäßig und kann sie von den intern verwendeten Containern entfernen. Mit boost::flyweights::no_tracking
wird dieser Erkennungsmechanismus ausgeschaltet. Das führt zu einer höheren Performance, bedeutet aber auch, dass intern verwendete Container nur größer werden können und nie kleiner.
Boost.Flyweight bietet weitergehende Einstellungsmöglichkeiten an. So kann zum Beispiel im Detail angegeben werden, welcher Container intern verwendet werden soll.
Verbessern Sie dieses Programm, indem Sie Boost.Flyweight verwenden. Setzen Sie die Hilfsmittel aus Boost.Flyweight mit deaktiviertem Support für Multithreading ein:
#include <string>
#include <vector>
#include <memory>
int main()
{
std::vector<std::shared_ptr<std::string>> countries;
auto germany = std::make_shared<std::string>("Germany");
for (int i = 0; i < 500; ++i)
countries.push_back(germany);
auto netherlands = std::make_shared<std::string>("Netherlands");
for (int i = 0; i < 500; ++i)
countries.push_back(netherlands);
}