
Hi, I'd like some help trying to crack a problem I've got. Now, I am using shared pointers at the moment, and that might be part of my problem, or it might not, but I'll describe the situation and you can tell to go elsewhere if you like! :) Part of my application involves scripting - allowing the user to write file based scripts to execute within the context of an application. I've designed it so it also gives the ability for the application writer to create some 'scripts' by creating classes directly in the C++ code. The script has the ability to run in the main thread of the app, but some statements could potentially block, so each statement is run from a 'timer' event which gets scheduled immediately. Those that block wait for an event and continue on that event. This part is working well. The statements are classes constructed in a hierarchical manner with things like a block class (a statement itself) which has a list of statements in it, an if statement class - which has an expression evaluator, a statement for the 'then' part, and an optional statement for the 'else' part. You get the idea? After looking at boost::lambda I thought it would be nice if I could construct my script similar to how lambda does these sorts of control statements - the if_, switch_, for_ constructs. The idea I have is like: expression exp = ... If_statement s1 = if_( expr)->then_( s2 )->else_( s3 ); while_statement ws = while_( expr ).do_( s1 ); One of the things I decided early on was to use shared_ptrs. The while_ and if_ constructs are functions returning a shared pointer to the real if statement object. Now my problem: given the example above. The do_ method of a while_statement takes a shared pointer to a statement so I don't have to worry about deallocation. Constructing the if_ statement on it's own is no problem, but if I was to construct one directly within the do_ part of the while statement it wont compile: while_( expr ).do_( if_(expr)->then_(s2)->else_(s3) ); The problem is to be able to chain then_ and else_ together, I have to return a plain pointer because the if_ statement itself doesn't know about it's shared pointer. So now, the compiler complains that can't implicitly convert a plain if_statement pointer into a shared pointer. And even if it could, I don't have the original shared pointer to hand. Now, I hope you're all still listening at the back! :) My first thought was to somehow get the object to look after the shared pointer, but surely if the object has a reference to itself, the ref count of the shared pointer could never reach zero? As I said right at the beginning, I think is a problem I have with shared pointers, whether it be a misunderstanding about them or maybe I'm using them the wrong way? I could always go for scrapping the shared pointers all together, and make the classes responsible for their own (de)allocation, but I was rather hoping to avoid that. Any tips and suggestions on ways forward will be much appreciated. (Even if to say - "ha! it'll never work", or "try a another newsgroup"! :) TIA. -- Regards, Steve.

On Sun, 08 Feb 2004 01:28:38 +0000, Steve Folly wrote
The problem is to be able to chain then_ and else_ together, I have to return a plain pointer because the if_ statement itself doesn't know about it's shared pointer. So now, the compiler complains that can't implicitly convert a plain if_statement pointer into a shared pointer. And even if it could, I don't have the original shared pointer to hand.
It looks like you can solve this by using intrusive pointers instead of shared pointers. The compiler can implicitly convert normal pointers into intrusive ones. Todd

On 7/2/04 9:37 pm, in article 20040207213053.M73385@www.flemingcnc.com, "todd" <todd@flemingcnc.com> wrote:
On Sun, 08 Feb 2004 01:28:38 +0000, Steve Folly wrote
The problem is to be able to chain then_ and else_ together, I have to return a plain pointer because the if_ statement itself doesn't know about it's shared pointer. So now, the compiler complains that can't implicitly convert a plain if_statement pointer into a shared pointer. And even if it could, I don't have the original shared pointer to hand.
It looks like you can solve this by using intrusive pointers instead of shared pointers. The compiler can implicitly convert normal pointers into intrusive ones.
Todd
Thanks for your reply, Todd. Ah - so since my object will always be holding a referece, and I can provide my intrusive_ptr_release function to release when the ref count is 1? Is that what you meant? And all my helper functions will be based on an intrusive pointer, rather than the shared pointer? I've noticed that the shared_ptr mechanism is thread safe, but the intrusive pointer isn't. I don't think that will be a problem because we enforce a policy of only accessing them from one thread anyway. I'll have a play. Cheers, Steve.

On Sun, 08 Feb 2004 09:59:21 +0000, Steve Folly wrote
Ah - so since my object will always be holding a referece, and I can provide my intrusive_ptr_release function to release when the ref count is 1? Is that what you meant?
Most implementations delete when the count falls to 0.
And all my helper functions will be based on an intrusive pointer, rather than the shared pointer?
Yes
I've noticed that the shared_ptr mechanism is thread safe, but the intrusive pointer isn't. I don't think that will be a problem because we enforce a policy of only accessing them from one thread anyway.
Why do you say that? Here's a simplified example (it lacks some border cases that most people don't need): ============ class ref_counted: boost::noncopyable { public: ref_counted(): num_refs(0) { } virtual ~ref_counted() { } long mutable volatile num_refs; }; inline void intrusive_ptr_add_ref(const ref_counted* p) { thread_safe_increment(p->num_refs); } inline void intrusive_ptr_release(const ref_counted* p) { if(!thread_safe_decrement(p->num_refs)) delete p; } ============ Here's how to use it: class my_class: public ref_counted { // ... }; intrusive_ptr<my_class> p = new my_class; You'll have to write the thread_safe_* functions for each OS you use. For Win32 (MSVC): extern "C" { long __cdecl _InterlockedIncrement(long volatile *); long __cdecl _InterlockedDecrement(long volatile *); } #pragma intrinsic (_InterlockedIncrement, _InterlockedDecrement) inline long thread_safe_increment(long volatile& i) { return _InterlockedIncrement(&i); } inline long thread_safe_decrement(long volatile& i) { return _InterlockedDecrement(&i); } The intrinsic interlocked functions are so fast (only 3 instructions ea.) that I don't bother using a non-thread-safe version for single-threaded apps. Todd

On 7/2/04 9:37 pm, in article 20040207213053.M73385@www.flemingcnc.com, "todd" <todd@flemingcnc.com> wrote:
On Sun, 08 Feb 2004 01:28:38 +0000, Steve Folly wrote
The problem is to be able to chain then_ and else_ together, I have to return a plain pointer because the if_ statement itself doesn't know about it's shared pointer. So now, the compiler complains that can't implicitly convert a plain if_statement pointer into a shared pointer. And even if it could, I don't have the original shared pointer to hand.
It looks like you can solve this by using intrusive pointers instead of shared pointers. The compiler can implicitly convert normal pointers into intrusive ones.
Todd
Hmmm... I've looked at our other posts in this thread and I don't think I've really explained my problem: I initially wanted to use shared pointers to not have to worry about memory deallocation. [please ignore syntax errors!] class statement { public: virtual void execute() = 0; }; typedef shared_ptr<statement> statement_p; class block_statement : public statement { public: block_statement (); block_statement_p add( statement_p s ) { statements.push_back( s ); return ????; } private: std::list<statement_p> statements; }; typedef shared_ptr<block_statement> block_statement_p; class if_statement : public statement { public: if_statement ( expression_p ); if_statement_p then_( statement_p s ) { then_statement = s; return ????; } if_statement_p else_( statement_p s ) { else_statement = s; return ????; } private: statement_p then_statement; statement_p else_statement; }; typedef shared_ptr<if_statement> if_statement_p; // helper functions block_statement_p block_() { return block_statement_p( new block_statement() ); } if_statement_p if_( expression_p e ) { return if_statement_p( new if_statement( e ) ); } So... To construct a block containing two if statements: block_p b = block_() ->add( if_( expression ) ->then( stmt2 ) ->else( stmt3 ) ) ->add( if_( expression ) ->then( stmt2 ) ->else( stmt3 ) ); My dilemma is - what do block_statement::add, if_statement::then and if_statement::else return? Since they have no knowledge of the shared pointer, the best they can do is to return 'this', but block_statement::add is expecting the shared pointer? One solution I'm considering is to do away with the shared pointers as parameters, but keep them internal to the classes (then_statement, else_statement, and block_statement::statements). The intent isn't to share statements between different parts of the script, for example creating a single if statement and inserting it into 2 different blocks. So perhaps block_statement::add should be: block_statement* add( statement* s ) { statements.push_back( statement_p( s ) ); return this; } and the helper function becomes block_statement* block_() { return new block_statement(); } Ok, there is *some* danger of duplicated pointers, but I can live with that it it's well documented. It would also makes the syntax (and implementation) nicer! Steve.

On Sun, 08 Feb 2004 20:47:41 +0000, Steve Folly wrote
Hmmm... I've looked at our other posts in this thread and I don't think I've really explained my problem:
Actually, I think you explained the problem quite well.
I initially wanted to use shared pointers to not have to worry about memory deallocation.
Yes. All smart pointers serve this purpose.
[please ignore syntax errors!]
ditto. class statement: public ref_counted { public: virtual void execute() = 0; }; typedef intrusive_ptr<statement> statement_p; // Look at this trick :) typedef intrusive_ptr<class block_statement> block_statement_p; class block_statement : public statement { public: block_statement (); block_statement_p add( statement_p s ) { statements.push_back( s ); return this; // conversion is implicit } private: std::list<statement_p> statements; }; typedef intrusive_ptr<class if_statement> if_statement_p; class if_statement : public statement { public: if_statement ( expression_p ); if_statement_p then_( statement_p s ) { then_statement = s; return this; } if_statement_p else_( statement_p s ) { else_statement = s; return this; } private: statement_p then_statement; statement_p else_statement; }; // helper functions block_statement_p block_() { return new block_statement(); // conversion is implicit } if_statement_p if_( expression_p e ) { return new if_statement( e ); // conversion is implicit } // same as what you had... block_p b = block_() ->add( if_( expression ) ->then( stmt2 ) ->else( stmt3 ) ) ->add( if_( expression ) ->then( stmt2 ) ->else( stmt3 ) );
My dilemma is - what do block_statement::add, if_statement::then and if_statement::else return? Since they have no knowledge of the shared pointer, the best they can do is to return 'this', but block_statement::add is expecting the shared pointer?
intrusive_ptr can be declared on a class that hasn't been defined yet. The functions shoud return an intrusive_ptr. I think I said earlier that they could return normal pointers; if so I made a mistake. That *might* cause premature deletion; the standard doesn't place a strong enough guarantee on the order of operations. i.e. a temporary intrusive_ptr might be destructed before another is constructed if those member functions return normal pointers. The call to add() in the example above automatically converts the intrusive_ptr<if_statement> to an intrusive_ptr<statement>; shared_ptr can't do that. This assumes that you are using a recent compiler. A caveat: don't be tempted to do the following, now that you know about the implicit conversions: block_p b = (new block_statement) ->add( (new if_statement( expression ) ) ->then( stmt2 ) ->else( stmt3 ) ) ->add( ( new if_statement( expression ) ) ->then( stmt2 ) ->else( stmt3 ) ); It will compile and run, but it may leak memory if an exception is thrown. Todd
participants (2)
-
Steve Folly
-
todd