Eigene Facetten

Die Localization-Library von C++ kann mit eigenen Facetten erweitert werden. Die können im Prinzip jegliches Verhalten realisieren, solange sie sich an einige kleine Details halten.

Video

Quelltext

#include <iostream>
#include <locale>
#include <string>

struct Quote
{
    std::string quote;
    std::string person;

    Quote(std::string const &q, std::string const &p)
        : quote { q }, person { p }
    {
    }
};

class QuotationFacet : public std::locale::facet
{
public:
    static std::locale::id id;
    
    std::string quote(Quote const &q) const
    {
        return doQuote(q);
    }
    
protected:
    
    virtual std::string doQuote(Quote const &q) const
    {
        return q.person+": \""+q.quote+"\"";
    }
};

std::locale::id QuotationFacet::id;

class NamedQuotationFacet : public QuotationFacet
{
    std::string name;
public:
    
    NamedQuotationFacet(std::string const &n)
        : name { n }
    {
    }
    
protected:
    
    std::string doQuote(Quote const &q) const override
    {
        if (name == "de") {
            return std::string("„") + q.quote + "“ " + "(" + q.person + ")";
        }
        else if (name == "fr") {
            return std::string("« ") + q.quote + " » " + "<" + q.person + ">";
        }
        else {
            return std::string("\"") + q.quote + "\" " + q.person;
        }
    }
};

std::ostream &operator<<(std::ostream &os, Quote const &q)
{
    if (std::has_facet<QuotationFacet>(os.getloc())) {
        auto &f = std::use_facet<QuotationFacet>(os.getloc());
        os << f.quote(q);
    }
    else {
        os << q.person << ": " << q.quote;
    }
    return os;
}

int main()
{
    Quote q { "We're made of star-stuff", "Carl Sagan" };
    
    std::cout << q << std::endl;
    
    std::cout.imbue(std::locale(std::cout.getloc(), new NamedQuotationFacet("de")));
    std::cout << q << std::endl;

    std::cout.imbue(std::locale(std::cout.getloc(), new NamedQuotationFacet("en")));
    std::cout << q << std::endl;
    
    std::cout.imbue(std::locale(std::cout.getloc(), new NamedQuotationFacet("fr")));
    std::cout << q << std::endl;
    
    std::cout.imbue(std::locale(std::cout.getloc(), new QuotationFacet()));
    std::cout << q << std::endl;
}

Erklärung

Ziel der Übung ist eine Facette, die Zitate (mehr oder minder) berühmter Personen länderspezifisch formatiert. Zu diesem Zweck implementieren wir das Konzept QuotationFacet. Diese Klasse übernimmt das Formatieren der Zitate, indem sie sie in einen String verwandelt. Die Methode quote() aus der öffentlichen Schnittstelle übernimmt ein Zitat und liefert die (ggf. länderspezifische) Darstellung als String zurück.

Als Basisfacette muss QuotationFacet zwei Bedingungen erfüllen: es muss von std::locale::facet abgeleitet sein und ein static-Feld namens id vom Typ std::locale::id enthalten. Sind diese erfüllt, dann kann ein Objekt dieser Klasse als Facette in einer std::locale gespeichert werden. Die gesamte weitere Schnittstelle ist für das Localization-Framework egal.

Von einer Basisfacette können abgeleitete Facetten erstellt werden. Diese können bspw. das durch die Basisfacette vorgegebene Konzept anders implementieren, so wie es hier im Beispiel die NamedQuotationFacet tut. Statt die Zitate immer auf die gleiche Art zu formatieren, passt die sich anhand ihres Namen an die Konventionen eines bestimmten Landes an.

Um eine Facette einer Locale hinzuzufügen, muss ein neues locale-Objekt erzeugt werden. Dieses kopiert eine bestehende Locale und übernimmt ein mittels new allokiertes Objekt der zusätzlichen Facette. Die Locale übernimmt dabei die Verantwortung für die Facette. Wird die letzte Locale mit dieser Facette gelöscht, so wird auch das Facettenobjekt entfernt.

Die Nutzung geschieht dann wie bei den Standardfacetten: mittels std::has_facet kann man prüfen, ob eine Facette in einer Locale enthalten ist und mittels std::use_facet eine Referenz auf diese Facette holen. Die Verwendung geschieht hier ähnlich wie bei std::numpunct automatisch im Ausgabeoperator.

Ähnlich wie bei den Standardfacetten greift hier die öffentliche Schnittstelle auf eine Sammlung von virtuellen Funktionen zurück, die das eigentliche Verhalten implementieren. Das vereinfacht die Erstellung von abgeleiteten Facetten, die dann nur die virtuellen Funktionen überschreiben.