Storing interfaces as void[]
tsbockman
thomas.bockman at gmail.com
Sat Mar 13 03:52:52 UTC 2021
On Saturday, 13 March 2021 at 00:36:37 UTC, David Zhang wrote:
> On Friday, 12 March 2021 at 22:18:59 UTC, tsbockman wrote:
>> You can use TypeInfo references as the keys for the struct
>> types, too. It's better than hashes because the TypeInfo is
>> already being generated anyway, and is guaranteed to be
>> unique, unlike your hashes which could theoretically collide.
>
> Makes sense. Using TypeInfo never occurred to me. I assume they
> are generated for COM classes as well?
I'm not sure about that; you should test it yourself. I know that
runtime type information support is incomplete for some
non-`extern(D)` types - for example:
https://issues.dlang.org/show_bug.cgi?id=21690
You can always fall back to fully qualified names for
non-`extern(D)` stuff if you have to.
But you should protect against hash collisions somehow, if you go
that route. Here's a hybrid approach that is immune to hash
collisions and works across DLL boundaries, but can almost always
verify equality with a single pointer comparison:
///////////////////////////////////////
struct TypeKey {
private:
const(void)* ptr;
size_t length;
this(const(TypeInfo) typeInfo) const pure @trusted nothrow
@nogc {
ptr = cast(const(void)*) typeInfo;
length = 0u;
}
this(string fqn) immutable pure @trusted nothrow {
/* We need to allocate a block of size_t to ensure proper
alignment
for the first chunk, which is the hash code of the fqn
string: */
size_t[] chunks = new size_t[1 + (fqn.length +
(size_t.sizeof - 1)) / size_t.sizeof];
chunks[0] = hashOf(fqn);
(cast(char*) chunks.ptr)[size_t.sizeof .. size_t.sizeof +
fqn.length] = fqn;
ptr = cast(immutable(void)*) chunks.ptr;
length = fqn.length;
}
@property const(TypeInfo) typeInfo() const pure @trusted
nothrow @nogc
in(length == 0u)
{
return cast(const(TypeInfo)) ptr;
}
@property size_t fqnHash() const pure @trusted nothrow @nogc
in(length != 0u)
{
return *cast(const(size_t)*) ptr;
}
@property string fqn() const pure @trusted nothrow @nogc
in(length != 0u)
{
const fqnPtr = cast(immutable(char)*) (this.ptr +
size_t.sizeof);
return fqnPtr[0 .. length];
}
public:
string toString() const @safe {
return (length == 0u)? typeInfo.toString() : fqn; }
size_t toHash() const @safe nothrow {
return (length == 0u)? typeInfo.toHash : fqnHash; }
bool opEquals(TypeKey that) const @trusted {
if(this.ptr is that.ptr)
return true;
if(this.length != that.length)
return false;
if(length == 0u)
return (this.typeInfo == that.typeInfo);
if(this.fqnHash != that.fqnHash)
return false;
return (this.fqn == that.fqn);
}
}
template typeKeyOf(Indirect)
if(is(Indirect : T*, T) // Support structs, static arrays,
etc.
|| is(Indirect == interface) || is(Indirect == class))
{
static if(is(Indirect : T*, T) || (__traits(getLinkage,
Indirect) == "D")) {
@property const(TypeKey) typeKeyOf() pure @safe nothrow
@nogc {
return const(TypeKey)(typeid(Indirect)); }
} else {
/* For FQN-based keys, ideally the whole process should
share a single
copy of the heap allocated fqn and its hash, so we'll use
a global. No
synchronization is necessary post construction, since it
is immutable: */
private immutable TypeKey masterKey;
shared static this() {
// With some bit-twiddling, this could be done at
compile time, if needed:
import std.traits : fullyQualifiedName;
masterKey =
immutable(TypeKey)(fullyQualifiedName!Indirect);
}
@property immutable(TypeKey) typeKeyOf() @safe nothrow
@nogc {
assert(masterKey.ptr !is null);
return masterKey;
}
}
}
@safe unittest {
static extern(C++) class X { }
static extern(C++) class Y { }
assert(typeKeyOf!(int*) == typeKeyOf!(int*));
assert(typeKeyOf!(int*) != typeKeyOf!(float*));
assert(typeKeyOf!X == typeKeyOf!X);
assert(typeKeyOf!X != typeKeyOf!Y);
assert(typeKeyOf!X != typeKeyOf!(int*));
assert(typeKeyOf!(float*) != typeKeyOf!Y);
}
///////////////////////////////////////
>> The lowering is something like this:
>> ...
> Makes sense, I always thought of them as existing in separate
> places.
Yeah, there are multiple reasonable ways of supporting interfaces
in a language, each with their own trade-offs. But, this is the
one used by D at the moment.
> So much head-bashing, and it's over. Thanks, tsbockman,
> Imperatorn.
You're very welcome!
More information about the Digitalmars-d-learn
mailing list