Escaping the Tyranny of the GC: std.rcstring, first blood

Dmitry Olshansky via Digitalmars-d digitalmars-d at puremagic.com
Sat Sep 27 02:38:09 PDT 2014


27-Sep-2014 02:51, Andrei Alexandrescu пишет:
> On 9/26/14, 2:50 PM, Dmitry Olshansky wrote:
>> 24-Sep-2014 18:55, Andrei Alexandrescu пишет:
>>> On 9/24/14, 3:31 AM, Dmitry Olshansky wrote:
>>>> 23-Sep-2014 19:13, Andrei Alexandrescu пишет:
>>>>> On 9/23/14, 12:17 AM, Dmitry Olshansky wrote:
>>>>>> In my imagination it would be along the lines of
>>>>>> @ARC
>>>>>> struct MyCountedStuff{ void opInc(); void opDec(); }
>>>>>
>>>>> So that would be a pointer type or a value type? Is there copy on
>>>>> write
>>>>> somewhere? -- Andrei
>>>>
>>>> It would be an intrusively counted type with pointer somewhere in the
>>>> body. To put it simply MyCountedStuff is a kind of smart pointer.
>>>
>>> Then that would be confusing seeing as structs are value types. What
>>> you're saying is that a struct with opInc() and opDec() has pointer
>>> semantics whereas one with not has value semantics. That design isn't
>>> going to fly.
>>
>> Read that as
>> struct RefCounted(T){
>>
>>      void opInc();
>>      void opDec();
>> }
>
> Consider:
>
> struct MyRefCounted
>      void opInc();
>      void opDec();
>      int x;
> }
>
> MyRefCounted a;
> a.x = 42;
> MyRefCounted b = a;
> b.x = 43;
>
> What is a.x after this?

Okay it serves no good for me to make these tiny comments while on the go.

As usual, structs are value types, so this feature can be mis-used, no 
two thoughts abouts it. It may need a bit of improvement in 
user-friendliness, compiler may help there by auto-detecting common misuse.

Theoretically class-es would be better choice, except that question of 
allocation pops up immediately, then consider for instance COM objects.

The good thing w.r.t. to memory about structs - they are themselves 
already allocated "somewhere", and it's only ref-counted payload that is 
allocated and destroyed in a user-defined way.

And now for the killer reasons to go for struct is the following:

Compiler _already_ does all of life-time management and had numerous bug 
fixes to make sure it does the right thing. In contrast there is nothing 
for classes that tracks their lifetimes to call proper hooks.

Let's REUSE that mechanism we have with structs and go as lightly as 
possible on  untested LOCs budget.

Full outline, of generic to the max, dirt-cheap implementation with a 
bit of lowering:

ARC or anything close to it, is implemented as follows:
1. Any struct that have @ARC attached, must have the following methods:
	void opInc();
	bool opDec(); // true - time to destroy
It also MUST NOT have postblit, and MUST have destructor.

2. Compiler takes user-defined destructor and creates proper destructor, 
as equivalent of this:
	if(opDec()){
		user__defined_dtor;
	}
3. postblit is defined as opInc().

4. any ctor has opInc() appended to its body.

Everything else is taken care of by the very nature of the structs.
Now this is enough to make ref-counted stuff a bit simpler to write but 
not much beyond. So here the next "consequences" that we can then implement:

4. Compiler is expected to assume anywhere in fully inlined code, that 
opInc()/opDec() pairs are no-op. It should do so even in debug mode 
(though there is less opportunity to do so without inlining). Consider 
it an NRVO of the new age, required optimization.

5. If we extend opInc/opDec to take an argument, the compiler may go 
further and batch up multiple opInc-s and opDec-s, as long as it's safe 
to do so (e.g. there could be exceptions thrown!):

Consider:

auto a = File("some-file.txt");
//pass to some structs for future use
B b = B(a);
C c = C(a);
a = File("other file");

May be (this is overly simplified!):

File a = void, b = void, c = void;
a = File.user_ctor("some-file.txt")'
a.opInc(2);
b = B(a);
c = C(a);
a = File.user_ctor("other file");
a.opInc();


-- 
Dmitry Olshansky


More information about the Digitalmars-d mailing list