[Block Pointer] Update
Hi, - So I've restructured all the code in the correct namespaces boost::smart_ptr & boost::smart_ptr::detail: https://github.com/philippeb8/block_ptr/tree/master/include/boost/smart_ptr - I am now using another smart pointer to refer to proxies because it is safer but the code is 15% slower than without it (now ~= shared_ptr<>): make: auto_ptr: 26211292 ns shared_ptr: 27291607 ns block_ptr: 61097868 ns new: auto_ptr: 23240569 ns shared_ptr: 48016207 ns block_ptr: 55554121 ns - Because of the removal of the proxy optimization there is a new caveat; if you assign an rvalue (temporary pointer on the stack) to a pointer on the heap (opposite of normal heap object assigned to stack pointer) then there the behavior will be undefined. I am looking to fix it or throw an exception but for now it's better not to use rvalues when assigning to pointer living on the heap as such: https://github.com/philippeb8/block_ptr/blob/master/example/t100_test1.cpp#L... Regards, -Phil
On 03/09/2016 11:28 PM, Phil Bouchard wrote:
Hi,
- So I've restructured all the code in the correct namespaces boost::smart_ptr & boost::smart_ptr::detail: https://github.com/philippeb8/block_ptr/tree/master/include/boost/smart_ptr
- I am now using another smart pointer to refer to proxies because it is safer but the code is 15% slower than without it (now ~= shared_ptr<>):
make: auto_ptr: 26211292 ns shared_ptr: 27291607 ns block_ptr: 61097868 ns
new: auto_ptr: 23240569 ns shared_ptr: 48016207 ns block_ptr: 55554121 ns
Note that it is possible & easy to use the fast_pool_allocator to allocate objects just by using the following line in benchmark.cpp: worker_new< boost::block_ptr<int>, fastblock<int> >(); Or: https://github.com/philippeb8/block_ptr/blob/master/example/benchmark.cpp#L9... And this way block_ptr<> will be 5% faster than shared_ptr<>: make: auto_ptr: 25673501 ns shared_ptr: 26853061 ns block_ptr: 61874962 ns new: auto_ptr: 22326728 ns shared_ptr: 47143526 ns block_ptr: 45007803 ns This is an unfair comparison but my point is using this fast allocator can be done easily.
- Because of the removal of the proxy optimization there is a new caveat; if you assign an rvalue (temporary pointer on the stack) to a pointer on the heap (opposite of normal heap object assigned to stack pointer) then there the behavior will be undefined. I am looking to fix it or throw an exception but for now it's better not to use rvalues when assigning to pointer living on the heap as such: https://github.com/philippeb8/block_ptr/blob/master/example/t100_test1.cpp#L...
I fixed the problem and now the correct way to use the pointer is by
passing around a proxy explicitly:
[...]
block_ptr
On 03/10/2016 07:47 PM, Phil Bouchard wrote:
On 03/09/2016 11:28 PM, Phil Bouchard wrote:
I fixed the problem and now the correct way to use the pointer is by passing around a proxy explicitly:
[...] block_ptr
t100 = new block ("I eat ([a-z]+) then drink ([a-z]+)"); t100->sub_[0].second = block_ptr (t100.proxy(), new block ("beef|chicken")); t100->sub_[1].second = block_ptr (t100.proxy(), new block ("vodka|water")); Or: https://github.com/philippeb8/block_ptr/blob/master/example/t100_test1.cpp#L...
I will fix make_block<>() to follow this syntax but this is how things should be done. Another useful example is by defining the internals of a container:
I fixed make_block<>() / make_fastblock<>() and now it looks like the
following:
[...]
block_ptr
On 03/10/2016 09:04 PM, Phil Bouchard wrote:
I fixed make_block<>() / make_fastblock<>() and now it looks like the following:
[...] block_ptr
t100 = make_block ("I eat ([a-z]+) then drink ([a-z]+)"); t100->sub_[0].second = make_block (t100.proxy(), "beef|chicken"); t100->sub_[1].second = make_block (t100.proxy(), "vodka|water");
I just discovered that I do not need the stack / heap detection algorithm anymore given proxies are now explicit. That'll save me a lot of trouble!
On 11/03/2016 16:50, Phil Bouchard wrote:
On 03/10/2016 09:04 PM, Phil Bouchard wrote:
I fixed make_block<>() / make_fastblock<>() and now it looks like the following:
[...] block_ptr
t100 = make_block ("I eat ([a-z]+) then drink ([a-z]+)"); t100->sub_[0].second = make_block (t100.proxy(), "beef|chicken"); t100->sub_[1].second = make_block (t100.proxy(), "vodka|water"); I just discovered that I do not need the stack / heap detection algorithm anymore given proxies are now explicit. That'll save me a lot of trouble!
I don't really understand the proxy thing, but I sense possible trouble in a single API name apparently creating different behaviour depending on the first parameter type, especially when the remaining parameters appear to be forwarded constructor parameters. (What happens if you want to construct an object that takes a proxy as a parameter?) Perhaps the first call should be to a method make_root_block and the second two to one called make_child_block, or something along those lines?
On 03/11/2016 01:31 AM, Gavin Lambert wrote:
On 11/03/2016 16:50, Phil Bouchard wrote:
I don't really understand the proxy thing, but I sense possible trouble in a single API name apparently creating different behaviour depending on the first parameter type, especially when the remaining parameters appear to be forwarded constructor parameters. (What happens if you want to construct an object that takes a proxy as a parameter?)
The proxy ensures all sub allocations will be wiped out when it is destructed, cyclic or not.
Perhaps the first call should be to a method make_root_block and the second two to one called make_child_block, or something along those lines?
I am fixing the pointer again and now the proxy will have to be fully
explicit:
struct list {
public:
list() : root(x) {}
void clear() {
root.reset();
}
void insert() {
if(root.get() == 0) {
root = make_block<node>(x, x);
} else {
root->next = make_block<node>(x, x);
root->next->prior = root;
root = root->next;
}
}
~list()
{
}
private:
block_proxy x;
block_ptr<node> root;
};
But I guess I could define a new type like:
struct list {
public:
[...]
private:
block_root_ptr<node> root;
};
Where block_root_ptr could be something like:
template <typename T>
struct block_root_ptr : private block_proxy, public block_ptr<T>
{
block_root_ptr() : block_proxy(),
block_ptr<T>(static_cast
On 12/03/2016 02:02, Phil Bouchard wrote:
On 03/11/2016 01:31 AM, Gavin Lambert wrote:
On 11/03/2016 16:50, Phil Bouchard wrote:
I don't really understand the proxy thing, but I sense possible trouble in a single API name apparently creating different behaviour depending on the first parameter type, especially when the remaining parameters appear to be forwarded constructor parameters. (What happens if you want to construct an object that takes a proxy as a parameter?)
The proxy ensures all sub allocations will be wiped out when it is destructed, cyclic or not.
So basically just an arena?
But I guess I could define a new type like:
struct list { public: [...] private: block_root_ptr<node> root; };
Where block_root_ptr could be something like:
template <typename T> struct block_root_ptr : private block_proxy, public block_ptr<T> { block_root_ptr() : block_proxy(), block_ptr<T>(static_cast
(*this)) {} }; Subsequent make_*() would follow the respective syntax. Thanks for your input...!
I'm not sure a new type is appropriate, since I thought part of the point was that consumers of the pointers don't know which one is the root, and separate arenas/proxies could end up getting merged based on usage. It just seems to be something required at construction time only. Also bear in mind that since people get into bad habits with passing smart pointers by value instead of by reference, any such type has to be slice-safe or you invite trouble. (ie. someone is going to construct a block_root_ptr and then pass it by value as a block_ptr, possibly destroying the original copy. In that second design, every single child block_ptr will explode at that point.)
On 03/13/2016 06:29 PM, Gavin Lambert wrote:
I'm not sure a new type is appropriate, since I thought part of the point was that consumers of the pointers don't know which one is the root, and separate arenas/proxies could end up getting merged based on usage. It just seems to be something required at construction time only.
It is possible to add the unification or proxies just like it was before but it won't be as clean as it is right now. I assume the C++ programmer knows how to program at a minimum. So I am trying to keep a balance here.
Also bear in mind that since people get into bad habits with passing smart pointers by value instead of by reference, any such type has to be slice-safe or you invite trouble. (ie. someone is going to construct a block_root_ptr and then pass it by value as a block_ptr, possibly destroying the original copy. In that second design, every single child block_ptr will explode at that point.)
I am not sure about all cases yet. I need to investigate.
On 03/13/2016 08:50 PM, Phil Bouchard wrote:
On 03/13/2016 06:29 PM, Gavin Lambert wrote:
I'm not sure a new type is appropriate, since I thought part of the point was that consumers of the pointers don't know which one is the root, and separate arenas/proxies could end up getting merged based on usage. It just seems to be something required at construction time only.
It is possible to add the unification or proxies just like it was before but it won't be as clean as it is right now. I assume the C++ programmer knows how to program at a minimum. So I am trying to keep a balance here.
It seems that I'll have to re-add the unification of proxies because what happens if you have bad code like: void foo(root_ptr<int>); root_ptr<int> p = new<int>(9); foo(p); cout << * p << endl; It won't work if I simple transfer ownership when copying root_ptrs. The good news is if I unify proxies then all the problems will be solved.
On 03/14/2016 05:58 AM, Phil Bouchard wrote:
On 03/13/2016 08:50 PM, Phil Bouchard wrote:
On 03/13/2016 06:29 PM, Gavin Lambert wrote:
I'm not sure a new type is appropriate, since I thought part of the point was that consumers of the pointers don't know which one is the root, and separate arenas/proxies could end up getting merged based on usage. It just seems to be something required at construction time only.
It is possible to add the unification or proxies just like it was before but it won't be as clean as it is right now. I assume the C++ programmer knows how to program at a minimum. So I am trying to keep a balance here.
It seems that I'll have to re-add the unification of proxies because what happens if you have bad code like:
void foo(root_ptr<int>);
root_ptr<int> p = new<int>(9);
foo(p);
cout << * p << endl;
It won't work if I simple transfer ownership when copying root_ptrs. The good news is if I unify proxies then all the problems will be solved.
Problem solved: https://github.com/philippeb8/root_ptr/blob/master/example/root_ptr_test1.cp...
On 03/13/2016 06:29 PM, Gavin Lambert wrote:
Also bear in mind that since people get into bad habits with passing smart pointers by value instead of by reference, any such type has to be slice-safe or you invite trouble. (ie. someone is going to construct a block_root_ptr and then pass it by value as a block_ptr, possibly destroying the original copy. In that second design, every single child block_ptr will explode at that point.)
I think you are right and I need to add a counter back to the proxy so that root_ptr<> (was block_ptr<>) can be r-values. Good one!
On 03/13/2016 09:42 PM, Phil Bouchard wrote:
On 03/13/2016 06:29 PM, Gavin Lambert wrote:
Also bear in mind that since people get into bad habits with passing smart pointers by value instead of by reference, any such type has to be slice-safe or you invite trouble. (ie. someone is going to construct a block_root_ptr and then pass it by value as a block_ptr, possibly destroying the original copy. In that second design, every single child block_ptr will explode at that point.)
I think you are right and I need to add a counter back to the proxy so that root_ptr<> (was block_ptr<>) can be r-values.
I just added support for r-values: root_ptr<int> foo() { return new node<int>(9); } int main() { cout << "R-value:" << endl; { cout << * foo() << endl; } cout << endl; } What happens there is if you copy a root_ptr<> to another one then it will transfer the contents of the internal intrusive list of node<>s to the new root_ptr<>.
On 14/03/2016 15:19, Phil Bouchard wrote:
On 03/13/2016 09:42 PM, Phil Bouchard wrote:
On 03/13/2016 06:29 PM, Gavin Lambert wrote:
Also bear in mind that since people get into bad habits with passing smart pointers by value instead of by reference, any such type has to be slice-safe or you invite trouble. (ie. someone is going to construct a block_root_ptr and then pass it by value as a block_ptr, possibly destroying the original copy. In that second design, every single child block_ptr will explode at that point.)
I think you are right and I need to add a counter back to the proxy so that root_ptr<> (was block_ptr<>) can be r-values.
I just added support for r-values:
root_ptr<int> foo() { return new node<int>(9); }
int main() { cout << "R-value:" << endl; { cout << * foo() << endl; } cout << endl; }
What happens there is if you copy a root_ptr<> to another one then it will transfer the contents of the internal intrusive list of node<>s to the new root_ptr<>.
What about slicing, though? Or have you removed the base-class association between the two?
On 03/13/2016 10:23 PM, Gavin Lambert wrote:
What about slicing, though? Or have you removed the base-class association between the two?
If I understand your question correctly then it works fine: struct B { int i; B() : i(9) {} }; struct C : B { }; void bar(node_ptr<B> p) { cout << p->i << endl; } int main() { cout << "Slicing:" << endl; { root_ptr<C> p = new node<C>(); bar(p); } cout << endl; } I need to take a break now ;)
participants (2)
-
Gavin Lambert
-
Phil Bouchard