@safe ref counted pointer
vit
vit at vit.vit
Wed Jan 12 21:17:13 UTC 2022
Hello,
I want implement @safe ref counted pointer (similar to
std::shared_ptr).
Problem is that access to managed data of ref counted pointer is
inherently unsafe because ref counted pointer can be released
(managed data is destroyed) and scope reference/pointer can be
still on the stack:
Example:
```d
import std.typecons;
scope rc = RefCounted!int(5);
(scope ref int data){
rc = RefCounted!int(42);
data = -1; ///dangling reference!
}(rc);
```
It look like i must choose from 2 options:
1) Method which can release managed data or move ownership of
managed data are @safe but methods accessing managed data must be
@system.
2) Method which can release managed data or move ownership of
managed data are @system but methods accessing managed data can
be @safe
Are this 2 options valid? (in -dip1000)
I implement ref counted pointer which use both options.
SharedPtr with otpion 1. and ScopedSharedPtr with option 2.
SharedPtr is convertable to ScopedSharedPtr by copy or move.
ScopedSharedPtr is convertable to SharedPtr by copy.
(Code ignore qualifiers, work only with integers, is thread local
only and has not custom allocators...)
Is use of @trusted in this code valid? (git link:
https://gist.github.com/run-dlang/1d18c71d0ad89409684969e7c155137e)
```d
import core.lifetime : forward, move, emplace;
import std.experimental.allocator.mallocator;
import std.traits : isIntegral, isMutable;
import std.stdio : writeln;
void main(){
scope SharedPtr!long top = SharedPtr!long.make(-1);
writeln("shared ptr:");
//shared ptr:
()@system{
scope SharedPtr!long x = SharedPtr!long.make(42);
(scope ref long data)@safe{
x.release(); ///release is @safe
x = SharedPtr!long.make(123); ///opAssign is @safe
top = move(x); ///move is @safe
//data = 314; ///dangling pointer
}(x.get); ///get is @system
}();
writeln("scoped shared ptr:");
///scoped shared ptr:
()@safe{
scope ScopedSharedPtr!long x = SharedPtr!long.make(654);
(scope ref long data)@safe{
//x.release(); ///release is @system
//x = SharedPtr!long.make(123); ///opAssign is @system
//top = move(x); ///opPostMove is @system
top = x; //copy is ok
data = -data; //cannot be dangling
pointer/reference
}(x.get); //get is @safe
}();
}
alias ScopedSharedPtr(T) = SharedPtr!(T, true);
struct SharedPtr(T, bool scoped = false)
if(isIntegral!T && isMutable!T){
//copy ctor:
this(scope ref typeof(this) rhs)@trusted{
if(rhs.impl){
this.impl = rhs.impl;
this.impl.counter += 1;
}
}
//forward ctor impl
this(bool s)(scope auto ref SharedPtr!(T, s) rhs,
typeof(null))@trusted{
if(rhs.impl){
this.impl = rhs.impl;
static if(__traits(isRef, rhs))
this.impl.counter += 1;
else
rhs.impl = null;
}
}
//forward ctor (constraint ignore move ctor)
this(bool s)(scope auto ref SharedPtr!(T, s) rhs)@trusted
if(__traits(isRef, rhs) || s != scoped){
if(rhs.impl){
this.impl = rhs.impl;
static if(__traits(isRef, rhs))
this.impl.counter += 1;
else
rhs.impl = null;
}
}
//forward assignment
void opAssign(bool s)(scope auto ref SharedPtr!(T, s)
rhs)scope{
if((()@trusted => cast(void*)&this is cast(void*)&rhs )())
return;
this.release();
if(rhs.impl){
()@trusted{
this.impl = rhs.impl;
}();
static if(__traits(isRef, rhs))
this.impl.counter += 1;
else
rhs.impl = null;
}
}
static auto make(Args...)(auto ref Args args)@safe{
return typeof(this)(Impl.construct(forward!args));
}
///ScopedSharedPtr:
static if(scoped){
//@system move:
void opPostMove(const ref typeof(this))@system{
}
//@system release
void release()scope @system{
this.release_impl();
}
//@safe get:
@property ref inout(T) get()inout return @safe pure
nothrow @nogc{
assert(impl !is null);
return impl.elm;
}
}
///SharedPtr:
else{
//@safe release
void release()scope @safe{
this.release_impl();
}
//@system get:
@property ref inout(T) get()inout return @system pure
nothrow @nogc{
assert(impl !is null);
return impl.elm;
}
}
~this()@safe{
this.release_impl();
}
private void release_impl()scope @safe{
if(impl){
impl.counter -= 1;
if(impl.counter == 0){
impl.destruct();
impl = null;
}
}
}
private alias Impl = ControlBlock!T;
private Impl* impl;
private this(Impl* impl)@safe{
this.impl = impl;
}
}
private struct ControlBlock(T){
T elm;
int counter;
static ControlBlock* construct(Args...)(auto ref Args
args)@safe{
writeln("alloc: ", args);
void[] data =
Mallocator.instance.allocate(ControlBlock.sizeof);
ControlBlock* impl = (()@trusted =>
cast(ControlBlock*)data.ptr )();
if(impl){
emplace(&impl.elm, forward!args);
impl.counter = 1;
}
return impl;
}
void destruct()@trusted{
writeln("delloc: ", elm);
destroy(elm);
const result =
Mallocator.instance.deallocate((cast(void*)&this)[0 ..
ControlBlock.sizeof]);
assert(result);
}
}
```
More information about the Digitalmars-d
mailing list