digitalmars.D - [OT] efficient return values
- Lionello Lunesu (74/74) Jan 25 2006 Hi,
- Oskar Linde (11/76) Jan 25 2006 This is what std::auto_ptr<> is perfect for. An auto_ptr will, using
- Lionello Lunesu (9/15) Jan 25 2006 Maybe the prototype of the Exec function could already mention the 'auto...
- Sean Kelly (6/10) Jan 25 2006 Yes, but it's not perfect if you wish to simply store a reference to
- Bruno Medeiros (7/95) Jan 31 2006 What do you mean "automatic like the c++ auto_ptr" ? What exactly are
- Oskar Linde (5/16) Jan 31 2006 Yes, but you can not by mistake make it a SqliteRS * if the function is ...
- Sean Kelly (48/53) Jan 25 2006 A lot of it has to do with the messed-up way the ODBC API was
- Lionello Lunesu (6/6) Jan 27 2006 When I think about it....
- Don Clugston (11/21) Jan 27 2006 The problem is, not all return values matter.
- Lionello Lunesu (23/24) Jan 27 2006 You shouldn't be using printf to begin with : S
- Don Clugston (9/29) Jan 27 2006 I'm not sure about that. This is a logical error, and ideally it would
- Sean Kelly (10/15) Jan 27 2006 That in itself is basically an illegal operation in C++, since throwing
- Don Clugston (12/32) Jan 28 2006 I know. The problem in this case was, (a) the destructor was calling a
- Lionello Lunesu (6/16) Jan 27 2006 I mean in the case of such a Lock call, where a return value of "false"
- Lionello Lunesu (14/14) Feb 01 2006 I'm still not clear on this issue. Please tell me how you people solve t...
Hi, This really should go in a C++ newsgroup, but I've noticed that I have more in common with the people here than on a C++ group. Must be all the frustrations with C/C++ and the hope for a better tomorrow. More than hope, an actual vision ; ) Here's a construct I can't seem to get right in C++. It works, but I hate the way I've implemented it. Maybe somebody has a better idea? It starts like this: I convert some handle into a class to make use of C++'s RAII, ctor/dtor. I do this all the time, but today it happens to be for sqlite3_get_table / sqlite3_free_table. Needless to say, the handle (char**) obtained by sqlite3_get_table must be freed by sqlite3_free_table. The wrapper for this table handle I've called SqliteRS (from Result Set): class SqliteRS { char** table; unsigned int cursor, rows, columns; public: SqliteRS(char** t, int c, int r) : table(t), cursor(c), columns(c), rows(r) {} ~SqliteRS() { sqlite3_free_table(table); } .... }; And you obtain an SqliteRS by calling Exec of the class SqliteDB, which wraps the database handle, sqlite3* (error handling omitted): SqliteRS SqliteDB::Exec( const char* zSql ) { sqlite3_get_table(pdb, zSql, &t, &r, &c, NULL ); return SqliteRS(t,c,r); } All this to be able to do: SqliteRS results = database.Exec("select * from X"); And everything should be freed nicely. The problem of course is that the temporary return value will actually be copied into the variable "results", causing the table to be freed when the temporary return value gets destructed, leaving "results" with an invalid handle. This is the problem I'm trying to solve. It's noteworthy that Visual Studio optimizes this code, even in debug mode! There won't be a temporary return value that gets copied to the actual variable. However, it won't compile if I create a private copy-ctor, eventhough it's never called when it's public. At the moment I have two ways around this: 1) create a copy ctor that steals the pointer (sets it to NULL in the temporary return value); or 2) create a seperate class for the return value and a ctor that takes this class in SqliteRS. 1) would be implemented like this: SqliteRS::SqliteRS( const SqliteRS &st ) { memcpy(this,&st,sizeof st); const_cast<SqliteRS&>(st).table = NULL; } 2) is a bit messier, but basically goes like this: SqliteDB::Exec returns an _SqliteRS with only the basic information (char**,int,int in this case). _SqliteRS has not dtor. SqliteRS becomes a ctor that takes an _SqliteRS, and the dtor that frees the handle. For added DBC one could add an assertion in the _SqliteRS dtor to assert that it was in fact copied into an SqliteRS, but this would require a const_cast<> similar to 1). Returning handles from other functions / factories happens only too often, and wrapping these handles in a class to make use of RAII (ctor/dtor) seems the way to go. But why is this contruction so tricky to do nicely? Am I forgetting something? In D the class will be created on the heap and you're returning the handle of the class, so there's never a copy involved. One can even use RAII: class SqliteDB { .... SqliteRS Exec( char[] sql ) { .... return new SqliteRS(...); } } auto SqliteRS result = database.Exec("..."); This works just fine. Lionello. By the way, has somebody else noticed that I can put an auto variable (the RAII kind) in a global handle and it still gets destructed when the 'auto' leaves the scope? Shouldn't the compiler complain?
Jan 25 2006
Lionello Lunesu wrote:Hi, This really should go in a C++ newsgroup, but I've noticed that I have more in common with the people here than on a C++ group. Must be all the frustrations with C/C++ and the hope for a better tomorrow. More than hope, an actual vision ; ) Here's a construct I can't seem to get right in C++. It works, but I hate the way I've implemented it. Maybe somebody has a better idea? It starts like this: I convert some handle into a class to make use of C++'s RAII, ctor/dtor. I do this all the time, but today it happens to be for sqlite3_get_table / sqlite3_free_table. Needless to say, the handle (char**) obtained by sqlite3_get_table must be freed by sqlite3_free_table. The wrapper for this table handle I've called SqliteRS (from Result Set): class SqliteRS { char** table; unsigned int cursor, rows, columns; public: SqliteRS(char** t, int c, int r) : table(t), cursor(c), columns(c), rows(r) {} ~SqliteRS() { sqlite3_free_table(table); } .... }; And you obtain an SqliteRS by calling Exec of the class SqliteDB, which wraps the database handle, sqlite3* (error handling omitted): SqliteRS SqliteDB::Exec( const char* zSql ) { sqlite3_get_table(pdb, zSql, &t, &r, &c, NULL ); return SqliteRS(t,c,r); } All this to be able to do: SqliteRS results = database.Exec("select * from X"); And everything should be freed nicely. The problem of course is that the temporary return value will actually be copied into the variable "results", causing the table to be freed when the temporary return value gets destructed, leaving "results" with an invalid handle. This is the problem I'm trying to solve.At the moment I have two ways around this: 1) create a copy ctor that steals the pointer (sets it to NULL in the temporary return value); or 2) create a seperate class for the return value and a ctor that takes this class in SqliteRS.[snip]Returning handles from other functions / factories happens only too often, and wrapping these handles in a class to make use of RAII (ctor/dtor) seems the way to go. But why is this contruction so tricky to do nicely? Am I forgetting something?This is what std::auto_ptr<> is perfect for. An auto_ptr will, using your words, steal the reference being assigned to it. A auto_ptr is perfect when you want to transfer responsibility.In D the class will be created on the heap and you're returning the handle of the class, so there's never a copy involved. One can even use RAII: class SqliteDB { .... SqliteRS Exec( char[] sql ) { .... return new SqliteRS(...); } } auto SqliteRS result = database.Exec("...");Yes, but with this, the user needs to remember to make the reference auto. There is (AFAIK) no way in D to make this automatic like the c++ auto_ptr does. If D had more power (assignment overloading, struct destructors/constructors), an auto_ptr would be straight-forward to implement in D too. /Oskar
Jan 25 2006
Maybe the prototype of the Exec function could already mention the 'auto'? auto SqliteRS Exec( char[] sql ) { ... } At the moment the compiler (correctly) complains with "functions cannot be const or auto". This kind of "auto" could also be used to force the caller to catch the return value in a variable. I guess exceptions can be used to prevent the caller from ignoring the result value. Exec("select * from X"); // not allowed; use ExecNonQuery Lio.auto SqliteRS result = database.Exec("...");Yes, but with this, the user needs to remember to make the reference auto. There is (AFAIK) no way in D to make this automatic like the c++ auto_ptr does. If D had more power (assignment overloading, struct destructors/constructors), an auto_ptr would be straight-forward to implement in D too.
Jan 25 2006
Oskar Linde wrote:This is what std::auto_ptr<> is perfect for. An auto_ptr will, using your words, steal the reference being assigned to it. A auto_ptr is perfect when you want to transfer responsibility.Yes, but it's not perfect if you wish to simply store a reference to something as a class member, because auto_ptr performs a destructive copy. Just something to be aware of for those who haven't used auto_ptr before. Sean
Jan 25 2006
Oskar Linde wrote:Lionello Lunesu wrote:What do you mean "automatic like the c++ auto_ptr" ? What exactly are the differences? In C++ you must also make the reference an auto_ptr,no? -- Bruno Medeiros - CS/E student "Certain aspects of D are a pathway to many abilities some consider to be... unnatural."Hi, This really should go in a C++ newsgroup, but I've noticed that I have more in common with the people here than on a C++ group. Must be all the frustrations with C/C++ and the hope for a better tomorrow. More than hope, an actual vision ; ) Here's a construct I can't seem to get right in C++. It works, but I hate the way I've implemented it. Maybe somebody has a better idea? It starts like this: I convert some handle into a class to make use of C++'s RAII, ctor/dtor. I do this all the time, but today it happens to be for sqlite3_get_table / sqlite3_free_table. Needless to say, the handle (char**) obtained by sqlite3_get_table must be freed by sqlite3_free_table. The wrapper for this table handle I've called SqliteRS (from Result Set): class SqliteRS { char** table; unsigned int cursor, rows, columns; public: SqliteRS(char** t, int c, int r) : table(t), cursor(c), columns(c), rows(r) {} ~SqliteRS() { sqlite3_free_table(table); } .... }; And you obtain an SqliteRS by calling Exec of the class SqliteDB, which wraps the database handle, sqlite3* (error handling omitted): SqliteRS SqliteDB::Exec( const char* zSql ) { sqlite3_get_table(pdb, zSql, &t, &r, &c, NULL ); return SqliteRS(t,c,r); } All this to be able to do: SqliteRS results = database.Exec("select * from X"); And everything should be freed nicely. The problem of course is that the temporary return value will actually be copied into the variable "results", causing the table to be freed when the temporary return value gets destructed, leaving "results" with an invalid handle. This is the problem I'm trying to solve.At the moment I have two ways around this: 1) create a copy ctor that steals the pointer (sets it to NULL in the temporary return value); or 2) create a seperate class for the return value and a ctor that takes this class in SqliteRS.[snip]Returning handles from other functions / factories happens only too often, and wrapping these handles in a class to make use of RAII (ctor/dtor) seems the way to go. But why is this contruction so tricky to do nicely? Am I forgetting something?This is what std::auto_ptr<> is perfect for. An auto_ptr will, using your words, steal the reference being assigned to it. A auto_ptr is perfect when you want to transfer responsibility.In D the class will be created on the heap and you're returning the handle of the class, so there's never a copy involved. One can even use RAII: class SqliteDB { .... SqliteRS Exec( char[] sql ) { .... return new SqliteRS(...); } } auto SqliteRS result = database.Exec("...");Yes, but with this, the user needs to remember to make the reference auto. There is (AFAIK) no way in D to make this automatic like the c++ auto_ptr does. /Oskar
Jan 31 2006
In article <dro5lt$14bf$1 digitaldaemon.com>, Bruno Medeiros says...Oskar Linde wrote:auto SqliteRS result = database.Exec("...");Yes, but with this, the user needs to remember to make the reference auto. There is (AFAIK) no way in D to make this automatic like the c++ auto_ptr does. /OskarWhat do you mean "automatic like the c++ auto_ptr" ? What exactly are the differences? In C++ you must also make the reference an auto_ptr,no?Yes, but you can not by mistake make it a SqliteRS * if the function is defined to return an auto_ptr<SqliteRS>. In D, if you forget the auto, everything will compile and run, but the resources are not guaranteed to be freed. /Oskar
Jan 31 2006
Lionello Lunesu wrote:Returning handles from other functions / factories happens only too often, and wrapping these handles in a class to make use of RAII (ctor/dtor) seems the way to go. But why is this contruction so tricky to do nicely? Am I forgetting something?A lot of it has to do with the messed-up way the ODBC API was written--it just doesn't map well into an object model. What I've done in the past is store the handles in an RAII-type object as you are: class db_handle_ptr : private noncopyable { public: SQLHDBC hdbc; db_handle_ptr() : hdbc( SQL_NULL_HDBC ) {} ~db_handle_ptr() { ::SQLDisconnect( hdbc ); ::SQLFreeHandle( SQL_HANDLE_DBC, hdbc ); } }; And then pass around references to it using a shared pointer (similar to the one available in Boost). I get around returning handles at all by instead returning a temporary object that is only accessible by the destination object: class resultset : private noncopyable { public: class resultset_init { public: resultset_init( shared_ptr<db_handle_ptr> const& hdb, shared_ptr<stmt_handle_ptr> const& hs ); private: friend resultset; shared_ptr<db_handle_ptr> m_db; shared_ptr<stmt_handle_ptr> m_stmt; }; resultset( resultset_init const& init ); private: shared_ptr<db_handle_ptr> m_db; shared_ptr<stmt_handle_ptr> m_stmt; }; class statement : private noncopyable { public: resultset::resultset_init open(); }; The end result is that all the handles that need to stay alive do so until all dependent objects are destroyed. So even if the connection object is deleted, any open statements or resultsets will remain usable. Sean
Jan 25 2006
When I think about it.... Shouldn't an ignored return value be treated as an error? Or at least a warning, or an assertion (like forgetting to return something). Maybe adding a construction like: void = I_dont_care_about_the_return_value(); L.
Jan 27 2006
Lionello Lunesu wrote:When I think about it.... Shouldn't an ignored return value be treated as an error? Or at least a warning, or an assertion (like forgetting to return something). Maybe adding a construction like: void = I_dont_care_about_the_return_value(); L.The problem is, not all return values matter. There are lots of APIs which have some really stupid return values. The classic example being printf(). void = printf("hello world\n"); No thanks. I think you'd need some kind of "must use" syntax on a function declaration, which would trigger an error if it wasn't used. Maybe a "nonvoid" keyword. But I don't think this is a very common programming error. Not really worth worrying about.
Jan 27 2006
void = printf("hello world\n");You shouldn't be using printf to begin with : S But I agree, not a common mistake. I was thinking about the waste of CPU time when somebody discards return codes. Fits nicely with D's the notion of "statement must have side effect", for example a "{strlen(a);}" compiles fine, but is a waste of time. I made the following template when I noticed that many colleagues were ignoring the return value of some Lock() call: //! Assert that a (return) value gets used (used for handles) template <typename T> class _use { const T retval; bool unused_return_value; public: _use( const T& rv ) : retval(rv), unused_return_value(false) {} ~_use() { assert(unused_return_value); } operator T () { unused_return_value=true; return retval; } }; I defined the call as _use<bool> Lock() and it now asserts when the return value is not caught into some variable or expression. I know, I should have used C++ exceptions, which I hope to do when we get more time on our hands... (probably never :S) Lio.
Jan 27 2006
Lionello Lunesu wrote:I made the following template when I noticed that many colleagues were ignoring the return value of some Lock() call: //! Assert that a (return) value gets used (used for handles) template <typename T> class _use { const T retval; bool unused_return_value; public: _use( const T& rv ) : retval(rv), unused_return_value(false) {} ~_use() { assert(unused_return_value); } operator T () { unused_return_value=true; return retval; } }; I defined the call as _use<bool> Lock() and it now asserts when the return value is not caught into some variable or expression.I know, I should haveused C++ exceptions, which I hope to do when we get more time on our hands... (probably never :S)I'm not sure about that. This is a logical error, and ideally it would be caught at compile time. I think an assert is the next best option. (Because even if the exception is caught, there's still a logic error). I'm a little biased against exceptions, because the worst bug I've ever encountered in my life was caused by a destructor which threw an exception, inside a multi-threaded program. (There were virtually no symptoms, that single thread just silently disappeared - yikes).
Jan 27 2006
Don Clugston wrote:I'm a little biased against exceptions, because the worst bug I've ever encountered in my life was caused by a destructor which threw an exception, inside a multi-threaded program. (There were virtually no symptoms, that single thread just silently disappeared - yikes).That in itself is basically an illegal operation in C++, since throwing an exception while another is in flight terminates the program. If in doubt, wrap the contents of your dtor in a try{}catch(...){} block, and report the error by some other means. I'll admit I do like that D doesn't have this problem, even if it means that some exceptions are overridden by others and are therefore never reported. I've wondered whether exception chaining via the .next property might be right for this purpose, but it's not a perfect fit. Sean
Jan 27 2006
Sean Kelly wrote:Don Clugston wrote:I know. The problem in this case was, (a) the destructor was calling a function which called another function (written by someone else) which eventually threw a destructor (it was not documented that it could throw), and (b) if it terminated the program, that would be OK. In this case, it only terminated the thread. Causing a sequence of further disasters. If inI'm a little biased against exceptions, because the worst bug I've ever encountered in my life was caused by a destructor which threw an exception, inside a multi-threaded program. (There were virtually no symptoms, that single thread just silently disappeared - yikes).That in itself is basically an illegal operation in C++, since throwing an exception while another is in flight terminates the program.doubt, wrap the contents of your dtor in a try{}catch(...){} block, and report the error by some other means.I learnt from this to _always_ be in doubt. It left me with in serious doubt of the merits of RAII when used with exceptions. It means that any kind of cleanup operation cannot use exceptions to report errors. I'll admit I do like that Ddoesn't have this problem, even if it means that some exceptions are overridden by others and are therefore never reported. I've wondered whether exception chaining via the .next property might be right for this purpose, but it's not a perfect fit. Sean
Jan 28 2006
I mean in the case of such a Lock call, where a return value of "false" means the lock failed and you shouldn't do anything with the object. That's way to important to do with a return value and should be an exception that can't simply be forgotten about.I know, I should have used C++ exceptions, which I hope to do when we get more time on our hands... (probably never :S)I'm not sure about that. This is a logical error, and ideally it would be caught at compile time. I think an assert is the next best option. (Because even if the exception is caught, there's still a logic error).I'm a little biased against exceptions, because the worst bug I've ever encountered in my life was caused by a destructor which threw an exception, inside a multi-threaded program. (There were virtually no symptoms, that single thread just silently disappeared - yikes).Simple rule: never throw in a dtor :-) Lio.
Jan 27 2006
I'm still not clear on this issue. Please tell me how you people solve the "using RAII for returned handles" problem. Do you use... a) method 1 from my original post: a wrapper with a copy ctor that steals the pointer b) method 2 from my original post: a separate class to temporarily wrap the return value c) std::auto_ptr<> and therefor a wrapped pointer on the heap d) making the factory class a friend of the wrapper class ...there are probably even more ways to do this thing. RAII (however bad that name may be) is the best thing that OO languages have to offer (if you ask me) but it frustrates me that I can't find a nice solution for transfering handle-ownership from a factory the handle-wrapper. Lionello.
Feb 01 2006