tracking_ptr.qbk
来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 173 行
QBK
173 行
[/ / Copyright (c) 2008 Eric Niebler / / Distributed under the Boost Software License, Version 1.0. (See accompanying / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) /][section Cycle collection with [^tracking_ptr<>]]In xpressive, regex objects can refer to each other and themselves by value or by reference.In addition, they ref-count their referenced regexes to keep them alive. This creates the possibilityfor cyclic reference counts, and raises the possibility of memory leaks. xpressive avoids leaksby using a type called `tracking_ptr<>`. This doc describes at a high level how `tracking_ptr<>`works.[h2 Constraints]Our solution must meet the following design constraints:* No dangling references: All objects referred to directly or indirectly must be kept alive as long as the references are needed.* No leaks: all objects must be freed eventually.* No user intervention: The solution must not require users to explicitly invoke some cycle collection routine.* Clean-up is no-throw: The collection phase will likely be called from a destructor, so it must never throw an exception under any circumstance. [h2 Handle-Body Idiom]To use `tracking_ptr<>`, you must separate your type into a handle and a body. In the case ofxpressive, the handle type is called `basic_regex<>` and the body is called `regex_impl<>`. Thehandle will store a `tracking_ptr<>` to the body.The body type must inherit from `enable_reference_tracking<>`. This gives the body the bookkeepingdata structures that `tracking_ptr<>` will use. In particular, it gives the body:# `std::set<shared_ptr<body> > refs_` : collection of bodies to which this body refers, and# `std::set<weak_ptr<body> > deps_` : collection of bodies which refer to this body.[h2 References and Dependencies]We refer to (1) above as the "references" and (2) as the "dependencies". It is crucial to theunderstanding of `tracking_ptr<>` to recognize that the set of references includes both thoseobjects that are referred to directly as well as those that are referred to indirectly (thatis, through another reference). The same is true for the set of dependencies. In other words,each body holds a ref-count directly to every other body that it needs. Why is this important? Because it means that when a body no longer has a handle referringto it, all its references can be released immediately without fear of creating dangling references. References and dependencies cross-pollinate. Here's how it works:# When one object acquires another as a reference, the second object acquires the first as a dependency.# In addition, the first object acquires all of the second object's references, and the second object acquires all of the first object's dependencies.# When an object picks up a new reference, the reference is also added to all dependent objects.# When an object picks up a new dependency, the dependency is also added to all referenced objects.# An object is never allowed to have itself as a dependency. Objects may have themselves as references, and often do.Consider the following code: sregex expr; { sregex group = '(' >> by_ref(expr) >> ')'; // (1) sregex fact = +_d | group; // (2) sregex term = fact >> *(('*' >> fact) | ('/' >> fact)); // (3) expr = term >> *(('+' >> term) | ('-' >> term)); // (4) } // (5)Here is how the references and dependencies propagate, line by line:[table[[Expression][Effects]][[1) `sregex group = '(' >> by_ref(expr) >> ')';`][[^group: cnt=1 refs={expr} deps={}\nexpr: cnt=2 refs={} deps={group}]]][[2) `sregex fact = +_d | group;`][[^group: cnt=2 refs={expr} deps={fact}\nexpr: cnt=3 refs={} deps={group,fact}\nfact: cnt=1 refs={expr,group} deps={}]]][[3) `sregex term = fact >> *(('*' >> fact) | ('/' >> fact));`][[^group: cnt=3 refs={expr} deps={fact,term}\nexpr: cnt=4 refs={} deps={group,fact,term}\nfact: cnt=2 refs={expr,group} deps={term}\nterm: cnt=1 refs={expr,group,fact} deps={}]]][[4) `expr = term >> *(('+' >> term) | ('-' >> term));`][[^group: cnt=5 refs={expr,group,fact,term} deps={expr,fact,term}\nexpr: cnt=5 refs={expr,group,fact,term} deps={group,fact,term}\nfact: cnt=5 refs={expr,group,fact,term} deps={expr,group,term}\nterm: cnt=5 refs={expr,group,fact,term} deps={expr,group,fact}]]][[5) `}`][[^expr: cnt=2 refs={expr,group,fact,term} deps={group,fact,term}]]]]This shows how references and dependencies propagate when creating cycles of objects. Afterline (4), which closes the cycle, every object has a ref-count on every other object, evento itself. So how does this not leak? Read on.[h2 Cycle Breaking]Now that the bodies have their sets of references and dependencies, the hard part is done.All that remains is to decide when and where to break the cycle. That is the job of `tracking_ptr<>`,which is part of the handle. The `tracking_ptr<>` holds 2 `shared_ptr`s. The first, obviously,is the `shared_ptr<body>` -- the reference to the body to which this handle refers. The other`shared_ptr` is used to break the cycle. It ensures that when all the handles to a body go outof scope, the body's set of references is cleared.This suggests that more than one handle can refer to a body. In fact, `tracking_ptr<>` givesyou copy-on-write semantics -- when you copy a handle, the body is shared. That makes copiesvery efficient. Eventually, all the handles to a particular body go out of scope. When thathappens, the ref count to the body might still be greater than 0 because some other body (orthis body itself!) might be holding a reference to it. However, we are certain that the cycle-breaker'sref-count goes to 0 because the cycle-breaker only lives in handles. No more handles, no morecycle-breakers.What does the cycle-breaker do? Recall that the body has a set of references of type `std::set<shared_ptr<body> >`. Let's call this type "references_type". The cycle-breaker is a`shared_ptr<references_type>`. It uses a custom deleter, which is defined as follows: template<typename DerivedT> struct reference_deleter { void operator ()(std::set<shared_ptr<DerivedT> > *refs) const { refs->clear(); } };The job of to the cycle breaker is to ensure that when the last handle to a body goes away,the body's set of references is cleared. That's it.We can clearly see how this guarantees that all bodies are cleaned up eventually. Once everyhandle has gone out of scope, all the bodies' sets of references will be cleared, leaving nonewith a non-zero ref-count. No leaks, guaranteed.It's a bit harder to see how this guarantees no dangling references. Imagine that there are 3 bodies: A, B and C. A refers to B which refers to C. Now all the handles to B go out of scope,so B's set of references is cleared. Doesn't this mean that C gets deleted, even though itis being used (indirectly) by A? It doesn't. This situation can never occur because we propagatedthe references and dependencies above such that A will be holding a reference directly to Cin addition to B. When B's set of references is cleared, no bodies get deleted, because theyare all still in use by A.[h2 Future Work]All these `std::set`s and `shared_ptr`s and `weak_ptr`s! Very inefficient. I used them becausethey were handy. I could probably do better.Also, some objects stick around longer than they need to. Consider: sregex b; { sregex a = _; b = by_ref(a); b = _; } // a is still alive here!Due to the way references and dependencies are propagated, the `std::set` of references can onlygrow. It never shrinks, even when some references are no longer needed. For xpressive thisisn't an issue. The graphs of referential objects generally stay small and isolated. If someonewere to try to use `tracking_ptr<>` as a general ref-count-cycle-collection mechanism, this problemwould have to be addressed.[endsect]
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?