Hat das Schlüsselwort 'mutable' einen anderen Zweck als das Ändern der Variablen durch eine const-Funktion?


527

Vor einiger Zeit bin ich auf Code gestoßen, der eine Mitgliedsvariable einer Klasse mit dem mutableSchlüsselwort markiert . Soweit ich sehen kann, können Sie damit einfach eine Variable in einer constMethode ändern :

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

Ist dies die einzige Verwendung dieses Schlüsselworts oder steckt mehr dahinter, als man denkt? Ich habe diese Technik seitdem in einer Klasse verwendet und eine boost::mutexals veränderlich markiert, damit constFunktionen sie aus Gründen der Thread-Sicherheit sperren können, aber um ehrlich zu sein, fühlt es sich wie ein Hack an.


2
Eine Frage: Wenn Sie nichts ändern, warum müssen Sie zuerst einen Mutex verwenden? Ich möchte das nur verstehen.
Misgevolution

@Misgevolution Sie ändern etwas, Sie steuern nur, wer / wie die Änderung über const vornehmen kann. Ein wirklich naives Beispiel: Stellen Sie sich vor, wenn ich Freunden nur nicht konstante Griffe gebe, erhalten Feinde einen konstanten Griff. Freunde können modifizieren, Feinde nicht.
Iheanyi

1
Hinweis: Hier ist ein großartiges Beispiel für die Verwendung des Schlüsselworts mutable: stackoverflow.com/questions/15999123/…
Gabriel Staples

Ich wünschte, es könnte zum Überschreiben const(von Typen) verwendet werden, damit ich dies nicht tun muss: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;Wenn ich standardmäßig const möchte, dh mutable A a;(explizit veränderbar) und A a;(implizite const).
AlfC

Antworten:


351

Es ermöglicht die Unterscheidung von bitweiser Konstante und logischer Konstante. Logische Konstante ist, wenn sich ein Objekt nicht auf eine Weise ändert, die über die öffentliche Schnittstelle sichtbar ist, wie in Ihrem Sperrbeispiel. Ein anderes Beispiel wäre eine Klasse, die beim ersten Anfordern einen Wert berechnet und das Ergebnis zwischenspeichert.

Da c ++ 11 mutableauf einem Lambda verwendet werden kann, um anzuzeigen, dass durch Wert erfasste Dinge modifizierbar sind (sie sind nicht standardmäßig):

int x = 0;
auto f1 = [=]() mutable {x = 42;};  // OK
auto f2 = [=]()         {x = 42;};  // Error: a by-value capture cannot be modified in a non-mutable lambda

52
'veränderlich' beeinflusst die bitweise / logische Konstanz überhaupt nicht. C ++ ist nur bitweise const und das Schlüsselwort 'mutable' kann verwendet werden, um Mitglieder von dieser Prüfung auszuschließen. Es ist in C ++ nur über Abstraktionen (z. B. SmartPtrs) möglich, eine 'logische' Konstante zu erreichen.
Richard Corden

111
@ Richard: Du verpasst den Punkt. Es gibt kein "logisches const" -Schlüsselwort, sondern eine konzeptionelle Differenzierung, die der Programmierer vornimmt, um zu entscheiden, welche Mitglieder ausgeschlossen werden sollen, indem er veränderlich gemacht wird, basierend auf dem Verständnis dessen, was den logisch beobachtbaren Zustand des Objekts ausmacht.
Tony Delroy

6
@ajay Ja, das ist der springende Punkt beim Markieren einer Mitgliedsvariablen als veränderlich, damit sie in const-Objekten geändert werden kann.
KeithB

6
Warum braucht man auf Lambdas veränderlich? Wäre es nicht ausreichend, Variablen als Referenz zu erfassen?
Giorgio

11
@Giorgio: Der Unterschied besteht darin, dass das xim Lambda modifizierte im Lambda bleibt, dh die Lambda-Funktion kann nur ihre eigene Kopie von modifizieren x. Die Änderung ist außen nicht sichtbar, das Original xbleibt unverändert. Bedenken Sie, dass Lambdas als Funktorklassen implementiert sind. erfasste Variablen entsprechen Mitgliedsvariablen.
Sebastian Mach

138

Das mutableSchlüsselwort ist eine Möglichkeit, den constSchleier zu durchbohren, den Sie über Ihre Objekte legen. Wenn Sie eine konstante Referenz oder Zeiger auf ein Objekt haben, können Sie dieses Objekt nicht in irgendeiner Weise ändern , außer , wann und wie es markiert ist mutable.

Mit Ihrer constReferenz oder Ihrem Zeiger sind Sie beschränkt auf:

  • Lesezugriff nur für sichtbare Datenelemente
  • Berechtigung, nur Methoden aufzurufen, die als gekennzeichnet sind const.

Mit der mutableAusnahme können Sie jetzt markierte Datenelemente schreiben oder festlegen mutable. Das ist der einzige äußerlich sichtbare Unterschied.

Intern können die constfür Sie sichtbaren Methoden auch in markierte Datenelemente schreiben mutable. Im Wesentlichen wird der konstante Schleier umfassend durchbohrt. Es liegt ganz beim API-Designer, sicherzustellen, dass mutabledas constKonzept nicht zerstört wird und nur in nützlichen Sonderfällen verwendet wird. Das mutableSchlüsselwort hilft, da es Datenelemente, die diesen Sonderfällen unterliegen, eindeutig kennzeichnet.

In der Praxis können Sie constIhre Codebasis zwanghaft verwenden (Sie möchten Ihre Codebasis im Wesentlichen mit der const"Krankheit" "infizieren "). In dieser Welt sind Zeiger und Referenzenconst mit sehr wenigen Ausnahmen , die Code liefern, der leichter zu verstehen und zu verstehen ist. Für einen interessanten Exkurs schauen Sie nach "referentielle Transparenz".

Ohne das mutableSchlüsselwort werden Sie möglicherweise gezwungen sein, const_castdie verschiedenen nützlichen Sonderfälle zu behandeln, die es zulässt (Caching, Ref-Counting, Debug-Daten usw.). Leider const_castist es wesentlich destruktiver als mutableweil es den API- Client zwingt , den constSchutz der von ihm verwendeten Objekte zu zerstören . Darüber hinaus führt dies zu einer weit verbreiteten constZerstörung: const_castWenn Sie einen konstanten Zeiger oder eine Referenz verwenden, können Sie uneingeschränkt schreiben und Methoden aufrufen, um auf sichtbare Elemente zuzugreifen. Im Gegensatz dazu mutablemuss der API-Designer die constAusnahmen genau steuern. In der Regel sind diese Ausnahmen in constMethoden verborgen , die mit privaten Daten arbeiten.

(Hinweis: Ich beziehe mich einige Male auf die Sichtbarkeit von Daten und Methoden . Ich spreche von Mitgliedern, die als öffentlich oder privat oder geschützt gekennzeichnet sind. Dies ist eine völlig andere Art des hier diskutierten Objektschutzes .)


8
Plus, mit const_casteinem Teil eines modifizieren constergibt Objekt nicht definiertes Verhalten.
Brian

Ich bin damit nicht einverstanden, da dies den API-Client zwingt, den konstanten Schutz der Objekte zu zerstören . Wenn Sie die const_castMutation von Mitgliedsvariablen in einer constMethode implementieren würden, würden Sie den Client nicht bitten, die Umwandlung durchzuführen - Sie würden dies innerhalb der Methode tun , indem Sie const_casting this. Grundsätzlich können Sie die Konstanz für beliebige Mitglieder an einer bestimmten Anrufstelle umgehen , während mutableSie die Konstanz für ein bestimmtes Mitglied an allen Anrufstellen entfernen können . Letzteres ist normalerweise das, was Sie für die typische Verwendung wünschen (Caching, Statistiken), aber manchmal passt der const_cast zum Muster.
BeeOnRope

1
Das const_castMuster passt in einigen Fällen besser, z. B. wenn Sie ein Mitglied vorübergehend ändern und dann wiederherstellen möchten (ähnlich wie boost::mutex). Die Methode ist logisch konstant, da der Endzustand mit dem Anfangszustand identisch ist, Sie diese vorübergehende Änderung jedoch vornehmen möchten. const_castkann dort nützlich sein, weil Sie damit const speziell in dieser Methode wegwerfen können, wenn die Mutation rückgängig gemacht wird, aber mutablenicht so angemessen wäre, da dadurch der const-Schutz von allen Methoden entfernt würde, die nicht unbedingt alle dem "do" folgen , "Muster rückgängig machen".
BeeOnRope

2
Die mögliche Platzierung eines von const definierten Objekts im Nur-Lese-Speicher (allgemeiner als Nur-Lese- Speicher gekennzeichnet ) und die zugehörige Standardsprache, die dies ermöglicht, machen jedoch const_casteine mögliche Zeitbombe möglich. mutablehat kein solches Problem, da solche Objekte nicht im Nur-Lese-Speicher abgelegt werden konnten.
BeeOnRope

75

Ihre Verwendung mit boost :: mutex ist genau das, wofür dieses Schlüsselwort bestimmt ist. Eine andere Verwendung ist das interne Zwischenspeichern von Ergebnissen, um den Zugriff zu beschleunigen.

Grundsätzlich gilt "veränderlich" für alle Klassenattribute, die den extern sichtbaren Zustand des Objekts nicht beeinflussen.

In dem Beispielcode in Ihrer Frage ist veränderlich möglicherweise unangemessen, wenn der Wert von done_ den externen Status beeinflusst. Dies hängt davon ab, was in ... enthalten ist. Teil.


35

Mutable dient zum Markieren eines bestimmten Attributs als innerhalb von constMethoden veränderbar. Das ist der einzige Zweck. Überlegen Sie sorgfältig, bevor Sie es verwenden, da Ihr Code wahrscheinlich sauberer und lesbarer ist, wenn Sie das Design ändern, anstatt es zu verwenden mutable.

http://www.highprogrammer.com/alan/rants/mutable.html

Also, wenn der obige Wahnsinn nicht das ist, wofür veränderlich ist, wofür ist er dann? Hier ist der subtile Fall: Veränderbar ist für den Fall, dass ein Objekt logisch konstant ist, sich aber in der Praxis ändern muss. Diese Fälle sind selten, aber es gibt sie.

Beispiele, die der Autor angibt, sind Caching- und temporäre Debugging-Variablen.


2
Ich denke, dieser Link ist das beste Beispiel für ein Szenario, in dem veränderlich hilfreich ist. Es scheint fast so, als würden sie ausschließlich zum Debuggen verwendet. (bei korrekter Verwendung)
enthusiasticgeek

Die Verwendung von mutablekann den Code lesbarer und sauberer machen. Im folgenden Beispiel readkann das constwie erwartet sein. `veränderlicher m_mutex; Container m_container; void add (Item item) {Lockguard lock (m_mutex); m_container.pushback (item); } Item read () const {Lockguard lock (m_mutex); return m_container.first (); } `
Th. Thielemann

Es gibt einen äußerst beliebten Anwendungsfall: Ref zählt.
Seva Alekseyev

33

Dies ist nützlich in Situationen, in denen Sie den internen Status ausgeblendet haben, z. B. in einem Cache. Zum Beispiel:

Klasse HashTable
{
...
Öffentlichkeit:
    String-Suche (String-Schlüssel) const
    {
        if (key == lastKey)
            return lastValue;

        Zeichenfolgenwert = lookupInternal (Schlüssel);

        lastKey = key;
        lastValue = value;

        Rückgabewert;
    }}

Privat:
    veränderbare Zeichenfolge lastKey, lastValue;
};

Und dann kann ein const HashTableObjekt weiterhin seine lookup()Methode verwenden, die den internen Cache ändert.


9

mutable existiert, wenn Sie daraus schließen, dass man Daten in einer ansonsten konstanten Funktion ändern kann.

Die Absicht ist, dass Sie möglicherweise eine Funktion haben, die "nichts" mit dem internen Status des Objekts tut, und Sie die Funktion markieren const, aber Sie müssen möglicherweise einige der Objektzustände so ändern, dass sie sich nicht auf den korrekten Status auswirken Funktionalität.

Das Schlüsselwort kann als Hinweis für den Compiler dienen. Ein theoretischer Compiler kann ein konstantes Objekt (z. B. ein globales Objekt) im Speicher ablegen, das als schreibgeschützt markiert wurde. Das Vorhandensein von mutableHinweisen, dass dies nicht getan werden sollte.

Hier einige gültige Gründe, um veränderbare Daten zu deklarieren und zu verwenden:

  • Gewindesicherheit. A zu deklarieren mutable boost::mutexist völlig vernünftig.
  • Statistiken. Zählen der Anzahl der Aufrufe einer Funktion anhand einiger oder aller ihrer Argumente.
  • Auswendiglernen. Berechnen Sie eine teure Antwort und speichern Sie sie dann zum späteren Nachschlagen, anstatt sie erneut zu berechnen.

2
Gute Antwort, außer dem Kommentar, dass veränderlich ein "Hinweis" ist. Dies lässt den Eindruck entstehen, dass das veränderbare Mitglied manchmal nicht veränderbar ist, wenn der Compiler das Objekt in das ROM gestellt hat. Das Verhalten von veränderlichen ist gut definiert.
Richard Corden

2
Abgesehen davon, dass ein const-Objekt im Nur-Lese-Speicher abgelegt wird, kann der Compiler beispielsweise auch entscheiden, const-Funktionsaufrufe aus einer Schleife heraus zu optimieren. Ein veränderbarer Statistikzähler in einer ansonsten konstanten Funktion ermöglicht weiterhin eine solche Optimierung (und zählt nur einen Aufruf), anstatt die Optimierung nur zu verhindern, um mehr Aufrufe zu zählen.
Hagen von Eitzen

@HagenvonEitzen - Ich bin mir ziemlich sicher, dass das falsch ist. Ein Compiler kann Funktionen nur dann aus einer Schleife herausheben, wenn er nachweisen kann, dass keine Nebenwirkungen vorliegen. Dieser Beweis beinhaltet im Allgemeinen, dass die Implementierung der Funktion tatsächlich überprüft wird (häufig nachdem sie inline ist) und sich nicht darauf verlässt const(und eine solche Überprüfung wird unabhängig von constoder erfolgreich sein oder fehlschlagen mutable). Es constreicht nicht aus , nur die Funktion zu deklarieren : Eine constFunktion kann Nebenwirkungen haben, z. B. das Ändern einer globalen Variablen oder etwas, das an die Funktion übergeben wird. Daher ist dies keine nützliche Garantie für diesen Beweis.
BeeOnRope

Einige Compiler haben spezielle Erweiterungen wie _attribute __ ((const)) und __attribute __ ((pure)) von gcc, die solche Auswirkungen haben , aber das hängt nur tangential mit dem constSchlüsselwort in C ++ zusammen.
BeeOnRope

8

Nun ja, das ist es, was es tut. Ich verwende es für Mitglieder, die durch Methoden geändert werden, die den Status einer Klasse nicht logisch ändern - zum Beispiel, um die Suche durch Implementierung eines Caches zu beschleunigen:

class CIniWrapper
{
public:
   CIniWrapper(LPCTSTR szIniFile);

   // non-const: logically modifies the state of the object
   void SetValue(LPCTSTR szName, LPCTSTR szValue);

   // const: does not logically change the object
   LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;

   // ...

private:
   // cache, avoids going to disk when a named value is retrieved multiple times
   // does not logically change the public interface, so declared mutable
   // so that it can be used by the const GetValue() method
   mutable std::map<string, string> m_mapNameToValue;
};

Jetzt müssen Sie dies mit Vorsicht verwenden - Parallelitätsprobleme sind ein großes Problem, da ein Aufrufer möglicherweise davon ausgeht, dass sie threadsicher sind, wenn nur constMethoden verwendet werden. Und natürlich sollte das Ändern von mutableDaten das Verhalten des Objekts nicht wesentlich verändern. Dies könnte durch das von mir angegebene Beispiel verletzt werden, wenn beispielsweise erwartet würde, dass auf die Festplatte geschriebene Änderungen für die App sofort sichtbar sind .


6

Mutable wird verwendet, wenn Sie eine Variable innerhalb der Klasse haben, die nur innerhalb dieser Klasse verwendet wird, um Dinge wie beispielsweise einen Mutex oder eine Sperre zu signalisieren. Diese Variable ändert das Verhalten der Klasse nicht, ist jedoch erforderlich, um die Thread-Sicherheit der Klasse selbst zu implementieren. Ohne "veränderlich" könnten Sie also keine "const" -Funktionen haben, da diese Variable in allen Funktionen geändert werden muss, die der Außenwelt zur Verfügung stehen. Daher wurde mutable eingeführt, um eine Mitgliedsvariable auch durch eine const-Funktion beschreibbar zu machen.

Die angegebene veränderbare Variable informiert sowohl den Compiler als auch den Leser darüber, dass es sicher ist und erwartet wird, dass eine Mitgliedsvariable innerhalb einer const-Mitgliedsfunktion geändert werden kann.


4

mutable wird hauptsächlich für ein Implementierungsdetail der Klasse verwendet. Der Benutzer der Klasse muss nichts darüber wissen, daher ist die Methode, die er für "sollte" hält, const. Ihr Beispiel dafür, dass ein Mutex veränderlich ist, ist ein gutes kanonisches Beispiel.


4

Ihre Verwendung ist es nicht ein Hack, obwohl wie viele Dinge in C ++, wandelbar kann Hack für einen faulen Programmierer sein , wer möchte nicht den ganzen Weg zurück und markieren Sie etwas gehen , die nicht const als nicht-const sein sollte.


3

Verwenden Sie "veränderlich", wenn für Dinge, die für den Benutzer LOGISCH zustandslos sind (und daher "const" -Getter in den APIs der öffentlichen Klasse enthalten sollten), in der zugrunde liegenden IMPLEMENTATION (dem Code in Ihrer .cpp) jedoch NICHT zustandslos sind.

Die Fälle, in denen ich es am häufigsten verwende, sind die verzögerte Initialisierung zustandsloser "einfacher alter Daten" -Mitglieder. Es ist nämlich ideal in engen Fällen, in denen es teuer ist, solche Mitglieder entweder zu bauen (Prozessor) oder herumzutragen (Speicher), und viele Benutzer des Objekts niemals nach ihnen fragen werden. In dieser Situation möchten Sie eine verzögerte Konstruktion im Back-End für die Leistung, da 90% der erstellten Objekte sie niemals erstellen müssen und Sie dennoch die richtige zustandslose API für den öffentlichen Verbrauch präsentieren müssen.


2

Mutable ändert die Bedeutung constvon bitweiser Konstante in logische Konstante für die Klasse.

Dies bedeutet, dass Klassen mit veränderlichen Elementen länger bitweise konstant sind und nicht mehr in schreibgeschützten Abschnitten der ausführbaren Datei angezeigt werden.

Darüber hinaus wird die Typprüfung constgeändert , indem Elementfunktionen veränderbare Elemente ohne Verwendung ändern können const_cast.

class Logical {
    mutable int var;

public:
    Logical(): var(0) {}
    void set(int x) const { var = x; }
};

class Bitwise {
    int var;

public:
    Bitwise(): var(0) {}
    void set(int x) const {
        const_cast<Bitwise*>(this)->var = x;
    }
};

const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.

int main(void)
{
    logical.set(5); // Well defined.
    bitwise.set(5); // Undefined.
}

Weitere Einzelheiten finden Sie in den anderen Antworten. Ich wollte jedoch hervorheben, dass dies nicht nur der Typensicherheit dient und sich auf das kompilierte Ergebnis auswirkt.


1

In einigen Fällen (wie bei schlecht gestalteten Iteratoren) muss die Klasse eine Zählung oder einen anderen zufälligen Wert beibehalten, der den Haupt- "Status" der Klasse nicht wirklich beeinflusst. Dies ist am häufigsten, wo ich veränderlich verwendet sehe. Ohne veränderlich wären Sie gezwungen, die gesamte Konstanz Ihres Designs zu opfern.

Es fühlt sich für mich auch die meiste Zeit wie ein Hack an. Nützlich in sehr wenigen Situationen.


1

Das klassische Beispiel (wie in anderen Antworten erwähnt) und die einzige Situation, in der ich das bisher mutableverwendete Schlüsselwort gesehen habe , ist das Zwischenspeichern des Ergebnisses eines kompliziertenGet Methode, bei der der Cache als Datenelement der Klasse und nicht als statische Variable in der Methode (aus Gründen der gemeinsamen Nutzung mehrerer Funktionen oder der einfachen Sauberkeit).

Im Allgemeinen sind die Alternativen zur Verwendung des mutableSchlüsselworts normalerweise eine statische Variable in der Methode oder im const_castTrick.

Eine weitere ausführliche Erklärung finden Sie hier .


1
Ich habe noch nie davon gehört, statische Mitglieder als allgemeine Alternative zu veränderlichen Mitgliedern zu verwenden. Und const_castnur, wenn Sie wissen (oder garantiert wurden), dass etwas nicht geändert wird (z. B. wenn Sie C-Bibliotheken stören) oder wenn Sie wissen, dass es nicht als const deklariert wurde. Das Ändern einer const-gegossenen const-Variablen führt zu undefiniertem Verhalten.
Sebastian Mach

1
@phresnel Mit "statischen Variablen" meinte ich statische automatische Variablen in der Methode (die über Aufrufe hinweg bleiben). Und const_castkann verwendet werden, um ein Klassenmitglied in einer constMethode zu ändern , worauf ich mich bezog ...
Daniel Hershcovich

1
Das war mir nicht klar, wie Sie "allgemein" geschrieben haben :) In Bezug auf das Ändern durch const_cast, wie gesagt, ist dies nur zulässig, wenn das Objekt nicht deklariert wurde const. ZB const Frob f; f.something();mit void something() const { const_cast<int&>(m_foo) = 2;Ergebnissen in undefiniertem Verhalten.
Sebastian Mach

1

Die veränderbare Variable kann nützlich sein, wenn Sie eine virtuelle const-Funktion überschreiben und Ihre untergeordnete Klassenmitgliedsvariable in dieser Funktion ändern möchten. In den meisten Fällen möchten Sie die Schnittstelle der Basisklasse nicht ändern, daher müssen Sie eine eigene veränderbare Mitgliedsvariable verwenden.


1

Das veränderbare Schlüsselwort ist sehr nützlich, wenn Sie Stubs für Klassentestzwecke erstellen. Sie können eine const-Funktion stubben und dennoch in der Lage sein, (veränderbare) Zähler oder andere Testfunktionen, die Sie Ihrem stub hinzugefügt haben, zu erhöhen. Dadurch bleibt die Schnittstelle der Stubbed-Klasse erhalten.


0

Eines der besten Beispiele, bei denen wir veränderlich verwenden, ist die tiefe Kopie. im Kopierkonstruktor senden wir const &objals Argument. Das neu erstellte Objekt ist also vom konstanten Typ. Wenn wir die Mitglieder in diesem neu erstellten const-Objekt ändern möchten (meistens werden wir sie nicht ändern, in seltenen Fällen können wir sie ändern), müssen wir es als deklarieren mutable.

mutableDie Speicherklasse kann nur für nicht statische nicht konstante Datenelemente einer Klasse verwendet werden. Das veränderbare Datenelement einer Klasse kann geändert werden, auch wenn es Teil eines Objekts ist, das als const deklariert ist.

class Test
{
public:
    Test(): x(1), y(1) {};
    mutable int x;
    int y;
};

int main()
{
    const Test object;
    object.x = 123;
    //object.y = 123;
    /* 
    * The above line if uncommented, will create compilation error.
    */   

    cout<< "X:"<< object.x << ", Y:" << object.y;
    return 0;
}

Output:-
X:123, Y:1

Im obigen Beispiel können wir den Wert der Mitgliedsvariablen ändern, xobwohl sie Teil eines Objekts ist, das als const deklariert ist. Dies liegt daran, dass die Variable xals veränderbar deklariert ist. Wenn Sie jedoch versuchen, den Wert der Mitgliedsvariablen zu ändern y, gibt der Compiler einen Fehler aus.


-1

Das Schlüsselwort 'mutable' ist eigentlich ein reserviertes Schlüsselwort. Oft wird es verwendet, um den Wert der konstanten Variablen zu variieren. Wenn Sie mehrere Werte einer Konstante haben möchten, verwenden Sie das Schlüsselwort mutable.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.