D: mutable Keyword
Einige kennen sicher das Keyword mutable aus C++.
In D gibt es kein entsprechendes Äquivalent, const bleibt auch const. Diese Gebenheit nervte mich in letzter Zeit jedoch etwas. Vor allen bei debugging/Log Sachen wäre es mal nützlich, einfach eine mutable Eigenschaft in einer als const Deklarierten Methode zu verwenden.
Für alle die es nicht wissen: eine const deklarierte Methode sieht folgendermaßen aus:
void foo() const {
//some code here}
Das const “sagt” dem Compiler sozusagen, dass diese Methode das Objekt an sich nicht verändert.
Allerdings: wenn eine Eigenschaft eines Objekts verändert wird, ändert sich doch der komplette Zustand des Objekts. Also wozu als mutable deklarieren?
Debugging oder Logging Mitschnitte (oder derartig vergleichbares) verändern nicht wirklich den Zustand, jedenfalls nicht ausschlaggebend. Und für Debugging immer wieder das const auszukommentieren und in der Release wieder hinzuzufügen kann schon nach einer kurzen Weile etwas “nerven”.
Daher habe ich mir eine kleine Struktur geschrieben und einen dazugehörigen Test der die Benutzung demonstriert.
Damit sollte das mutable aus C++ emuliert werden. Eventuell hat ja jemand Interesse daran:
private import std.stdio; private import std.conv; final struct mutable(T) { private: const T _value; void _assign(U)(U val) const { //writeln("private assign: " ~ to!(string)(val)); T * copy = cast(T*) &this._value; *copy = cast(T) val; } public: this(T val) { this._value = val; } this(const ref mutable!(T) val) { this._value = val.value; } mutable!(T) opAssign(U)(U val) const if (std.traits.isIntegral!(U) || std.traits.isSomeString!(U) || is(U == enum)) { this._assign(cast(T) val); return this; } mutable!(T) opAssign(U)(/+const ref +/mutable!(U) val) const { this._assign(val.value); return this; } mutable!(T) opOpAssign(string op, U)(U val) const { switch (op) { case "+": writeln("ADD"); U res = this._value + val; this._assign(res); break; case "-": writeln("SUB"); U res = this._value - val; this._assign(res); break; case "*": writeln("MUL"); U res = this._value * val; this._assign(res); break; case "/": writeln("DIV"); U res = this._value / val; this._assign(res); break; default: break; } return this; } mutable!(U) opBinary(string op, U)(U val) const { //mutable!(U) m; switch (op) { case "+": writeln("Bin. ADD"); U res = this._value + val; //m = res; return mutable!(U)(res); //break; case "-": writeln("Bin. SUB"); U res = this._value - val; //m = res;+ return mutable!(U)(res); //break; case "*": writeln("Bin. MUL"); U res = this._value * val; //writeln(typeid(U)); //m = res; return mutable!(U)(res); //break; case "/": writeln("Bin. DIV"); U res = this._value / val; //m = res; return mutable!(U)(res); //break; default: break; } //return m; assert(0); } bool opEquals(U)(const ref mutable!(U) m) const { if (m is null) return false; if (m is this) return true; return m.value == this._value; } bool opEquals(U)(U m) const { return m == this._value; } @property T value() const { writeln("VALUE: ", this._value); return this._value; } string toString() const { return to!(string)(this._value); } } enum Status { Stopped, Paused, Playing } class B { private: mutable!(ubyte) _id; mutable!(Status) _status; public: void pause() const { this._status = Status.Paused; } void stop() const { this._status = Status.Stopped; } void play() const { this._status = Status.Playing; } Status get_status() const { return this._status.value; } void setup(const ubyte val) const { _id = val; writefln("(=) ID: %d", _id); _id = _id.value + 2; writefln("(+) ID: %d", _id); _id += 2; writefln("(+=) ID: %d", _id); _id -= 32; writefln("(-=) ID: %d", _id); _id *= 2; writefln("(*=) ID: %d", _id); _id = _id + 2; writefln("(!+!) ID: %d", _id); _id = _id - 32; writefln("(!-!) ID: %d", _id); _id = _id * 1.2; writefln("(!*!) ID: %d", _id); writeln(typeid(typeof(_id.value))); _id = _id / 2.0; writefln("(!/!) ID: %d", _id); writeln(typeid(typeof(_id.value))); } } // in der main: B b = new B(); b.setup(128); writeln(b.get_status()); b.play(); writeln(b.get_status()); b.pause(); writeln(b.get_status()); b.play(); writeln(b.get_status()); b.stop(); writeln(b.get_status()); b.play(); writeln(b.get_status());
Nachtrag vom 12.01.2012
Ich habe die obige Struktur nochmal überarbeitet und an den Feinheiten geschliffen. Zudem habe ich nun daraus eine Klasse gemacht (geschah eher nach belieben als aus einem konkretem Grund).
Hier ist der Code:
private import std.stdio; private import std.conv : to; static private import std.traits; final class Mutable(T) { private: const T _value; void _assign(U)(const U val) const { T * copy = cast(T*) &this._value; *copy = cast(T) val; } public: this(const T value) { this._value = value; } this(const ref T value) { this._value = value; } void set_value(U)(U val) { this._assign(val); } @property T value() const { return cast(T) this._value; } alias value this; @disable static Mutable!(T) opAssign(U)(U val) { return null;//new Mutable!(T)(val); } const(Mutable!(T)) opAssign(U)(U val) const { if (is(T == Mutable)) { throw new Exception("Can't assign Mutable to Mutable!"); } this._assign(val); return this; } const(Mutable!(T)) opOpAssign(string op, U)(U val) const { switch (op) { case "+": //writeln("ADD"); U result = this._value + val; this._assign(result); break; case "-": //writeln("SUB"); U result = this._value - val; this._assign(result); break; case "*": //writeln("MUL"); U result = this._value * val; this._assign(result); break; case "/": //writeln("DIV"); U result = this._value / val; this._assign(result); break; default: break; } return this; } Mutable!(U) opBinary(string op, U)(U val) const { switch (op) { case "+": U result = this._value + val; return new Mutable!(U)(result); case "-": U result = this._value - val; return new Mutable!(U)(result); case "*": U result = this._value * val; return new Mutable!(U)(result); case "/": U result = this._value / val; return new Mutable!(U)(result); default: break; } assert(0); } bool opEquals(const T cmp) const { return cmp == this._value; } bool opEquals(const Mutable!(T) cmp) const { return cmp.value == this._value; } override string toString() const { return to!(string)(this._value); } } enum Status { Stopped, Paused, Playing } class B { private: const Mutable!(ubyte) _id; // mit "const" wird sichergestellt, dass _id beim ersten Aufruf initialisiert wird. const Mutable!(Status) _status; public: this() { this._id = new Mutable!(ubyte)(0); this._status = new Mutable!(Status)(Status.Stopped); } void pause() const { this._status = Status.Paused; } void stop() const { this._status = Status.Stopped; } void play() const { this._status = Status.Playing; } Status get_status() const { return this._status.value; } void setup(const ubyte val) const { _id = val; writefln("(=) ID: %d", _id); _id = _id.value + 2; writefln("(+) ID: %d", _id); _id += 2; writefln("(+=) ID: %d", _id); _id -= 32; writefln("(-=) ID: %d", _id); _id *= 2; writefln("(*=) ID: %d", _id); _id = _id + 2; writefln("(!+!) ID: %d", _id); _id = _id - 32; writefln("(!-!) ID: %d", _id); _id = _id * 1.2; writefln("(!*!) ID: %d", _id); writeln(typeid(typeof(_id.value))); writeln(typeid(typeof(_id))); assert(_id == 204); if (_id == 204) { writeln("Korrekt"); } _id = _id / 2.0; writefln("(!/!) ID: %d", _id); writeln(typeid(typeof(_id.value))); writeln(typeid(typeof(_id))); assert(_id == 102); } } void main() { B b = new B(); b.setup(128); writeln(b.get_status()); b.play(); writeln(b.get_status()); b.pause(); writeln(b.get_status()); b.play(); writeln(b.get_status()); b.stop(); writeln(b.get_status()); b.play(); writeln(b.get_status()); }