|
Packit Service |
7770af |
# LibSass smart pointer implementation
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
LibSass uses smart pointers very similar to `shared_ptr` known
|
|
Packit Service |
7770af |
by Boost or C++11. Implementation is a bit less modular since
|
|
Packit Service |
7770af |
it was not needed. Various compile time debug options are
|
|
Packit Service |
7770af |
available if you need to debug memory life-cycles.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
## Memory Classes
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### SharedObj
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
Base class for the actual node implementations. This ensures
|
|
Packit Service |
7770af |
that every object has a reference counter and other values.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
class AST_Node : public SharedObj { ... };
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### SharedPtr (base class for SharedImpl)
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
Base class that holds on to the pointer. The reference counter
|
|
Packit Service |
7770af |
is stored inside the pointer object directly (`SharedObj`).
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### SharedImpl (inherits from SharedPtr)
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
This is the main base class for objects you use in your code. It
|
|
Packit Service |
7770af |
will make sure that the memory it points at will be deleted once
|
|
Packit Service |
7770af |
all copies to the same object/memory go out of scope.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
Class* pointer = new Class(...);
|
|
Packit Service |
7770af |
SharedImpl<Class> obj(pointer);
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
To spare the developer of typing the templated class every time,
|
|
Packit Service |
7770af |
we created typedefs for each available AST Node specialization.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
typedef SharedImpl<Number> Number_Obj;
|
|
Packit Service |
7770af |
Number_Obj number = SASS_MEMORY_NEW(...);
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
## Memory life-cycles
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### Pointer pickups
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
I often use the terminology of "pickup". This means the moment when
|
|
Packit Service |
7770af |
a raw pointer not under any control is assigned to a reference counted
|
|
Packit Service |
7770af |
object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be
|
|
Packit Service |
7770af |
automatically released once the object goes out of scope (but only
|
|
Packit Service |
7770af |
if the reference counter reaches zero). Main point beeing, you don't
|
|
Packit Service |
7770af |
have to worry about memory management yourself.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### Object detach
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
Sometimes we can't return reference counted objects directly (see
|
|
Packit Service |
7770af |
invalid covariant return types problems below). But we often still
|
|
Packit Service |
7770af |
need to use reference objects inside a function to avoid leaks when
|
|
Packit Service |
7770af |
something throws. For this you can use `detach`, which basically
|
|
Packit Service |
7770af |
detaches the pointer memory from the reference counted object. So
|
|
Packit Service |
7770af |
when the reference counted object goes out of scope, it will not
|
|
Packit Service |
7770af |
free the attached memory. You are now again in charge of freeing
|
|
Packit Service |
7770af |
the memory (just assign it to a reference counted object again).
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
## Circular references
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
Reference counted memory implementations are prone to circular references.
|
|
Packit Service |
7770af |
This can be addressed by using a multi generation garbage collector. But
|
|
Packit Service |
7770af |
for our use-case that seems overkill. There is no way so far for users
|
|
Packit Service |
7770af |
(sass code) to create circular references. Therefore we can code around
|
|
Packit Service |
7770af |
this possible issue. But developers should be aware of this limitation.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
There are AFAIR two places where circular references could happen. One is
|
|
Packit Service |
7770af |
the `sources` member on every `Selector`. The other one can happen in the
|
|
Packit Service |
7770af |
extend code (Node handling). The easy way to avoid this is to only assign
|
|
Packit Service |
7770af |
complete object clones to these members. If you know the objects lifetime
|
|
Packit Service |
7770af |
is longer than the reference you create, you can also just store the raw
|
|
Packit Service |
7770af |
pointer. Once needed this could be solved with weak pointers.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
## Addressing the invalid covariant return types problems
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
If you are not familiar with the mentioned problem, you may want
|
|
Packit Service |
7770af |
to read up on covariant return types and virtual functions, i.e.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers
|
|
Packit Service |
7770af |
- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers
|
|
Packit Service |
7770af |
- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
We hit this issue at least with the CRTP visitor pattern (eval, expand,
|
|
Packit Service |
7770af |
listize and so forth). This means we cannot return reference counted
|
|
Packit Service |
7770af |
objects directly. We are forced to return raw pointers or we would need
|
|
Packit Service |
7770af |
to have a lot of explicit and expensive upcasts by callers/consumers.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### Simple functions that allocate new AST Nodes
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
In the parser step we often create new objects and can just return a
|
|
Packit Service |
7770af |
unique pointer (meaning ownership clearly shifts back to the caller).
|
|
Packit Service |
7770af |
The caller/consumer is responsible that the memory is freed.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
typedef Number* Number_Ptr;
|
|
Packit Service |
7770af |
int parse_integer() {
|
|
Packit Service |
7770af |
... // do the parsing
|
|
Packit Service |
7770af |
return 42;
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
Number_Ptr parse_number() {
|
|
Packit Service |
7770af |
Number_Ptr p_nr = SASS_MEMORY_NEW(...);
|
|
Packit Service |
7770af |
p_nr->value(parse_integer());
|
|
Packit Service |
7770af |
return p_nr;
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
Number_Obj nr = parse_number();
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
The above would be the encouraged pattern for such simple cases.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### Allocate new AST Nodes in functions that can throw
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
There is a major caveat with the previous example, considering this
|
|
Packit Service |
7770af |
more real-life implementation that throws an error. The throw may
|
|
Packit Service |
7770af |
happen deep down in another function. Holding raw pointers that
|
|
Packit Service |
7770af |
we need to free would leak in this case.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
int parse_integer() {
|
|
Packit Service |
7770af |
... // do the parsing
|
|
Packit Service |
7770af |
if (error) throw(error);
|
|
Packit Service |
7770af |
return 42;
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
With this `parse_integer` function the previous example would leak memory.
|
|
Packit Service |
7770af |
I guess it is pretty obvious, as the allocated memory will not be freed,
|
|
Packit Service |
7770af |
as it was never assigned to a SharedObj value. Therefore the above code
|
|
Packit Service |
7770af |
would better be written as:
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
typedef Number* Number_Ptr;
|
|
Packit Service |
7770af |
int parse_integer() {
|
|
Packit Service |
7770af |
... // do the parsing
|
|
Packit Service |
7770af |
if (error) throw(error);
|
|
Packit Service |
7770af |
return 42;
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
// this leaks due to pointer return
|
|
Packit Service |
7770af |
// should return Number_Obj instead
|
|
Packit Service |
7770af |
// though not possible for virtuals!
|
|
Packit Service |
7770af |
Number_Ptr parse_number() {
|
|
Packit Service |
7770af |
Number_Obj nr = SASS_MEMORY_NEW(...);
|
|
Packit Service |
7770af |
nr->value(parse_integer()); // throws
|
|
Packit Service |
7770af |
return &nr; // Ptr from Obj
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
Number_Obj nr = parse_number();
|
|
Packit Service |
7770af |
// will now be freed automatically
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
The example above unfortunately will not work as is, since we return a
|
|
Packit Service |
7770af |
`Number_Ptr` from that function. Therefore the object allocated inside
|
|
Packit Service |
7770af |
the function is already gone when it is picked up again by the caller.
|
|
Packit Service |
7770af |
The easy fix for the given simplified use case would be to change the
|
|
Packit Service |
7770af |
return type of `parse_number` to `Number_Obj`. Indeed we do it exactly
|
|
Packit Service |
7770af |
this way in the parser. But as stated above, this will not work for
|
|
Packit Service |
7770af |
virtual functions due to invalid covariant return types!
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### Return managed objects from virtual functions
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
The easy fix would be to just create a new copy on the heap and return
|
|
Packit Service |
7770af |
that. But this seems like a very inelegant solution to this problem. I
|
|
Packit Service |
7770af |
mean why can't we just tell the object to treat it like a newly allocated
|
|
Packit Service |
7770af |
object? And indeed we can. I've added a `detach` method that will tell
|
|
Packit Service |
7770af |
the object to survive deallocation until the next pickup. This means
|
|
Packit Service |
7770af |
that it will leak if it is not picked up by consumer.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
typedef Number* Number_Ptr;
|
|
Packit Service |
7770af |
int parse_integer() {
|
|
Packit Service |
7770af |
... // do the parsing
|
|
Packit Service |
7770af |
if (error) throw(error);
|
|
Packit Service |
7770af |
return 42;
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
Number_Ptr parse_number() {
|
|
Packit Service |
7770af |
Number_Obj nr = SASS_MEMORY_NEW(...);
|
|
Packit Service |
7770af |
nr->value(parse_integer()); // throws
|
|
Packit Service |
7770af |
return nr.detach();
|
|
Packit Service |
7770af |
}
|
|
Packit Service |
7770af |
Number_Obj nr = parse_number();
|
|
Packit Service |
7770af |
// will now be freed automatically
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
## Compile time debug options
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
To enable memory debugging you need to define `DEBUG_SHARED_PTR`.
|
|
Packit Service |
7770af |
This can i.e. be done in `include/sass/base.h`
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
```c++
|
|
Packit Service |
7770af |
define DEBUG_SHARED_PTR
|
|
Packit Service |
7770af |
```
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
This will print lost memory on exit to stderr. You can also use
|
|
Packit Service |
7770af |
`setDbg(true)` on sepecific variables to emit reference counter
|
|
Packit Service |
7770af |
increase, decrease and other events.
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
## Why reinvent the wheel when there is `shared_ptr` from C++11
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
First, implementing a smart pointer class is not really that hard. It
|
|
Packit Service |
7770af |
was indeed also a learning experience for myself. But there are more
|
|
Packit Service |
7770af |
profound advantages:
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
- Better GCC 4.4 compatibility (which most code still has OOTB)
|
|
Packit Service |
7770af |
- Not thread safe (give us some free performance on some compiler)
|
|
Packit Service |
7770af |
- Beeing able to track memory allocations for debugging purposes
|
|
Packit Service |
7770af |
- Adding additional features if needed (as seen in `detach`)
|
|
Packit Service |
7770af |
- Optional: optimized weak pointer implementation possible
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
### Thread Safety
|
|
Packit Service |
7770af |
|
|
Packit Service |
7770af |
As said above, this is not thread safe currently. But we don't need
|
|
Packit Service |
7770af |
this ATM anyway. And I guess we probably never will share AST Nodes
|
|
Packit Service |
7770af |
across different threads.
|