Use boost::shared_ptr with boost::adaptors
The following code gives me segfault. But if `foo_ptr` is `foo*` it works
fine.
#include <vector>
#include
On Sat, Apr 28, 2012 at 7:10 AM, tolik levchik
The following code gives me segfault. But if `foo_ptr` is `foo*` it works fine.
#include <vector> #include
#include #include #include #include struct foo{ foo(int _i): i(_i){} virtual void bar() const{ std::cout << "foo::bar::i " << i << std::endl; } int i; };
typedef boost::shared_ptr<foo> foo_ptr; //typedef foo* foo_ptr; foo_ptr trasform(int i){ return foo_ptr(new foo(i)); } int main(){ std::vector<int> vec; vec.push_back(1);
using namespace boost::adaptors; using boost::bind; boost::for_each(vec | transformed(bind(&trasform, _1)) | indirected, bind(&foo::bar, _1)); } Is it expected behavior of adaptors ?
I think so. Here's my guess what's going on. Consider what the dereference function might look like for an iterator to the range vec | transformed(bind(&transform, _1)) | indirected foo& dereference() { int& i = *vec_it; // dereference iterator to vec foo_ptr = transform(i); // pass i through bind(&transform, _1) foo& x = *foo_ptr; // pass foo_ptr through indirected return x; } Sadly, the only foo_ptr "backing" the foo& goes out of scope once the foo& is returned, so that foo object is deleted and you get a dangling reference. I don't think there's an obvious alternate implementation that will allow you to get away with the above. When you start chaining together range adaptors, especially transforms, you have to consider whether the argument to the transformation function needs to persist for the result to be remain valid. HTH, - Jeff
Thank you for explanation.
2012/4/29 Jeffrey Lee Hellrung, Jr.
On Sat, Apr 28, 2012 at 7:10 AM, tolik levchik
wrote: The following code gives me segfault. But if `foo_ptr` is `foo*` it works fine.
#include <vector> #include
#include #include #include #include struct foo{ foo(int _i): i(_i){} virtual void bar() const{ std::cout << "foo::bar::i " << i << std::endl; } int i; };
typedef boost::shared_ptr<foo> foo_ptr; //typedef foo* foo_ptr; foo_ptr trasform(int i){ return foo_ptr(new foo(i)); } int main(){ std::vector<int> vec; vec.push_back(1);
using namespace boost::adaptors; using boost::bind; boost::for_each(vec | transformed(bind(&trasform, _1)) | indirected, bind(&foo::bar, _1)); } Is it expected behavior of adaptors ?
I think so. Here's my guess what's going on. Consider what the dereference function might look like for an iterator to the range
vec | transformed(bind(&transform, _1)) | indirected
foo& dereference() { int& i = *vec_it; // dereference iterator to vec foo_ptr = transform(i); // pass i through bind(&transform, _1) foo& x = *foo_ptr; // pass foo_ptr through indirected return x; }
Sadly, the only foo_ptr "backing" the foo& goes out of scope once the foo& is returned, so that foo object is deleted and you get a dangling reference.
I don't think there's an obvious alternate implementation that will allow you to get away with the above. When you start chaining together range adaptors, especially transforms, you have to consider whether the argument to the transformation function needs to persist for the result to be remain valid.
HTH,
- Jeff
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
On Sat, Apr 28, 2012 at 9:45 PM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
Is it expected behavior of adaptors ?
I think so. Here's my guess what's going on. Consider what the dereference function might look like for an iterator to the range
vec | transformed(bind(&transform, _1)) | indirected
foo& dereference() { int& i = *vec_it; // dereference iterator to vec foo_ptr = transform(i); // pass i through bind(&transform, _1) foo& x = *foo_ptr; // pass foo_ptr through indirected return x; }
Sadly, the only foo_ptr "backing" the foo& goes out of scope once the foo& is returned, so that foo object is deleted and you get a dangling reference.
I don't think there's an obvious alternate implementation that will allow you to get away with the above. When you start chaining together range adaptors, especially transforms, you have to consider whether the argument to the transformation function needs to persist for the result to be remain valid.
When you put it that way I can see your point, but just from the code the OP presented the intermediate shared_ptr's should persist until the end of the expression. Just musing here, but how about if the dereference function you imagined returned a proxy reference, to keep the shared_ptr alive? Not sure how I'd implement that in practise, since it would be required to know foo_ptr was a shared_ptr, but maybe it could done at the level of the indirected adaptor rather than the indirection iterator. - Rob.
On Mon, Apr 30, 2012 at 3:05 AM, Robert Jones
On Sat, Apr 28, 2012 at 9:45 PM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
Is it expected behavior of adaptors ?
I think so. Here's my guess what's going on. Consider what the dereference function might look like for an iterator to the range
vec | transformed(bind(&transform, _1)) | indirected
foo& dereference() { int& i = *vec_it; // dereference iterator to vec foo_ptr = transform(i); // pass i through bind(&transform, _1) foo& x = *foo_ptr; // pass foo_ptr through indirected return x; }
Sadly, the only foo_ptr "backing" the foo& goes out of scope once the foo& is returned, so that foo object is deleted and you get a dangling reference.
I don't think there's an obvious alternate implementation that will allow you to get away with the above. When you start chaining together range adaptors, especially transforms, you have to consider whether the argument to the transformation function needs to persist for the result to be remain valid.
When you put it that way I can see your point, but just from the code the OP presented the intermediate shared_ptr's should persist until the end of the expression.
I'm not sure what you mean here; do you mean there's a bug on the boost side of the things in the original code listing?
Just musing here, but how about if the dereference function you imagined returned a proxy reference, to keep the shared_ptr alive? Not sure how I'd implement that in practise, since it would be required to know foo_ptr was a shared_ptr, but maybe it could done at the level of the indirected adaptor rather than the indirection iterator.
Something like that might be possible but the reality is that this problem persists regardless of whether you use shared_ptr or not. AFAIK, there's no way to generically and automatically detect whether return_type foo() { return f(g(bar)); } is "safe" (e.g., leaves no dangling references) for arbitrary combinations of f and g (even assuming bar's scope is beyond foo's), no matter what kind of acrobats you use to detect rvalues and lvalues or whatever, and if you go with a conservative implementation in any scenario, it's *probably* going to give sub-optimal performance in real use cases. That's just my guess and intuition... - Jeff
On Mon, Apr 30, 2012 at 11:13 PM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Mon, Apr 30, 2012 at 3:05 AM, Robert Jones
wrote: When you put it that way I can see your point, but just from the code the OP presented the intermediate shared_ptr's should persist until the end of the expression.
I'm not sure what you mean here; do you mean there's a bug on the boost side of the things in the original code listing?
Hmmm...., a bug... no... that might be overstating it I think. From your imagined function it's just an intrinsic limitation of a pragmatic implementation. But what I meant was that the OP's problematic statement was boost::for_each(vec | transformed(bind(&trasform, _1)) | indirected, bind(&foo::bar, _1)); which is a single expression, so it's reasonable to anticipate that 'temporary' shared_ptrs created in the course of evaluating this expression should persist until the end of the expression. Ok, I know there's lots of holes in that if you analyse it carefully, not least that the shared_ptrs in question are not ever explicit, so the rules aren't required to apply, but it might be reasonable to expect that they would persist. I'm really not nailing my colours to the mast here, just speculating, so be gentle! - Rob.
On Tue, May 1, 2012 at 1:09 AM, Robert Jones
On Mon, Apr 30, 2012 at 11:13 PM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Mon, Apr 30, 2012 at 3:05 AM, Robert Jones
wrote: When you put it that way I can see your point, but just from the code the OP presented the intermediate shared_ptr's should persist until the end of the expression.
I'm not sure what you mean here; do you mean there's a bug on the boost side of the things in the original code listing?
Hmmm...., a bug... no... that might be overstating it I think. From your imagined function it's just an intrinsic limitation of a pragmatic implementation.
Okay, just checking! But what I meant was that the OP's problematic statement was
boost::for_each(vec | transformed(bind(&trasform, _1)) | indirected, bind(&foo::bar, _1));
which is a single expression, so it's reasonable to anticipate that 'temporary' shared_ptrs created in the course of evaluating this expression should persist until the end of the expression. Ok, I know there's lots of holes in that if you analyse it carefully, not least that the shared_ptrs in question are not ever explicit, so the rules aren't required to apply, but it might be reasonable to expect that they would persist.
I'm really not nailing my colours to the mast here, just speculating, so be gentle!
I thought this was what you meant, and I sympathize :) It is nontrivial and opaque why that construct will not work, and I'd even agree that it's reasonable to expect it to work. This does leave me with a somewhat unsatisfactory feeling, but...I don't immediately see a satisfactory fix (other than "don't do that") :/ - Jeff
participants (3)
-
Jeffrey Lee Hellrung, Jr.
-
Robert Jones
-
tolik levchik