Re: [boost] Review of a safer memory management approach for C++?

David, Much more discussion of this topic over email is likely not very useful at this point but I will make some very brief responses below.
14. Re: FW: Boost Digest, Vol 2929, Issue 4 (David Abrahams)
------------------------------
Message: 14 Date: Fri, 04 Jun 2010 09:42:36 -0400 From: David Abrahams <dave@boostpro.com> To: boost@lists.boost.org Subject: Re: [boost] FW: Boost Digest, Vol 2929, Issue 4 Message-ID: <m2hbliewxf.wl%dave@boostpro.com> Content-Type: text/plain; charset=UTF-8
At Thu, 3 Jun 2010 17:29:15 -0600, Bartlett, Roscoe A wrote:
David,
I have some responses below ...
Message: 13 Date: Tue, 01 Jun 2010 16:16:25 -0400 From: David Abrahams <dave@boostpro.com>
I'm all for making essential complexity obvious and eliminating accidental complexity, but I'm not sure I see a compelling example that Teuchos does that, at least not when compared with what I consider normal state-of-the-art C++ programming, which is mostly value-based, dynamic allocations are rare, and those that occur are immediately managed, e.g. by appropriate smart pointers.
[Bartlett, Roscoe A]
I don't want to get into an argument about the "proper" way to use C++ here (but I will a little below). In Item 1 of "Effective C++, 3rd Edition", Scott Meyers identifies four different major programming paradigms that are supposed to be supported by C++:
1) C: raw pointers, low-level built-in datatypes (int, double, etc.), statements, blocks, functions etc.
2) Object-oriented C++: Inheritance, virtual functions, dynamic allocation, RTTI, etc. (i.e. runtime polymorphism)
3) Template C++: Typical generic programming, template metaprogramming, etc. (i.e. compile-time polymorphism)
4) STL: Containers, iterators, algorithms, etc. and code like this (not just using STL) (closely related to #3 Template C++).
Yes, I'm aware of that way of dividing up the picture.
Of course any well written large C++ program is a mixture of all four of the above (and more).
I don't take it for granted that any well-written large C++ program will use any given paradigm.
What you seem to be arguing above is that most good C++ software will use templates in #3 and #4 but almost none of #2 object-oriented C++.
Did I say anything about templates? No.
I _will_ argue that OOP has costs to software complexity that are often under-appreciated.
[Bartlett, Roscoe A] Any approach can be misused, including anything you might be trying to advocate (but I am not 100% sure what that is exactly).
Note that C++ was first designed in the early 80s primarily to support #2 object-oriented C++. I can see the reason for this bias for #3 and #4 given the nature of most of the Boost libraries and you can have that opinion if you would like.
I don't, so please read what I wrote and try to address that rather than to your preconceived notions of my ?bias.?
[Bartlett, Roscoe A] I am not trying to put words in your mouth. I am just trying to pin down your argument/position. You can say that I have a "bias" for good software if you would like :-) Seriously, I am sorry if I misinterpreted you position with the position of others on the boost list who were arguing for a more template-heavy approach. I guess that you are arguing for more of a stack-based approach (see below).
I would argue that the current accepted approaches to writing OO programs in C++ (i.e. #2 OO C++ features) make it too easy to create programs with undefined behavior (i.e. segfaults or worse) and as a result, the paranoia of undefined behavior in C++, memory leaks etc. lead to lots of bad programming practices and designs (see Section 1 in http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf).
Seriously? Are you saying that a disciplined application of shared_ptr, weak_ptr, new_shared, et. al when dynamic allocations are called for is insufficient for most OO programs?
[Bartlett, Roscoe A] As argued in the Teuchos MM document http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf these boost classes are a good start but they are *not* sufficient to build a safe and effective memory management system that encapsulated *all* raw pointers. What is missing for single objects, as described in Section 5.14 and other sections, is the Teuchos::Ptr class. Without integrating a class like Ptr in with the reference-counted pointer classes, you can't build a safe and effective memory management system in C++ that comes closer to eliminating undefined behavior. Also, as described in Section 5.9.2, separating weak_ptr from shared_ptr is not ideal from a design perspective since it reduces the generality and reusability of software (see the arguments for this in the document). Did you read at least the sections called out in the reduced table of contents given in the Preface? I think these sections should address all of these questions (and more). As for arrays, the current software in C++0x and boost is less sufficient (again, see Section 5.14 and the sections it refers to). What is in boost and C++0x is a good start but it has not gone far enough (i.e. the encapsulation of *all* raw pointers in *all* code, except where non-compliant code must be called of course).
Trying to over design a program to avoid all shared ownership is what make C++ programming so unproductive and has all the negative consequences described in Section 1 in:
http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf
Designs with object sharing can be much less complex overall than designs without sharing. You just need decent tools to detect
circular
reference problems (and that is what the Teuchos::RCP class has).
Well, I fundamentally disagree with all of the above. Overuse of runtime polymorphism, thus dynamic allocation, and thus shared ownership (it's almost an inevitable progression) is one of the things that has made C++ programming unproductive, and those people I know who most zealously avoid it tend to be more productive than everyone else. IMO.
[Bartlett, Roscoe A]
When runtime performance or other issues related to dynamic allocations and classic OO are not a problem, classic OO C++ programs using runtime polymorphism are typically superior to highly templated C++ programs using static polymorphism
Straw man; I'm not arguing for ?templated C++ programs using static polymorphism.?
[Bartlett, Roscoe A] Okay, the runtime vs. compile-time polymorphism debate is another issue (but a very important one). Are you instead arguing for a static and stack-based approach to programming? I haven see people try this and it leads to very inflexible software that is hard to maintain, reuse, and achieve optimal performance. One particularly good (or bad) example of this approach that I have seen is given in: http://www.trip.caam.rice.edu/software/rvldoc/rvl.html If you look deep in the RVL code, you will see a few Gotchas that destroy the true safely that is supposed to be afforded by the approach. Actually, ironically, they use stack-based object to try to avoid memory management problems (but not sufficiently) but yet at the same time, they rely on other types of mutable shared objects with implicit updates that make my very unconformable.
In my experience, designs with object sharing tend to increase complexity in all kinds of ways beyond the memory management issues that you describe. Perhaps the most important way is, they tend to obscure the overall computation being performed by hiding it in networks of interacting objects.
[Bartlett, Roscoe A]
This is not going to magically change by templating everything.
Straw man; I'm not arguing for ?templating everything.?
If a lot of objects collaborate together to perform computations, it does not matter if you use static or runtime polymorphism; the code will be hard to figure out and you will need print statements and/or run it in a debugger to see what is really happening.
Yes, and an ?OO mindset? has tended to lead to partitioning systems so that ?a lot of objects collaborate together to perform computations,? which tends to obscure the computation performed.
[Bartlett, Roscoe A] Yes but good OO design tries to minimize the web of object dependences. There are lots of good references on the need and methods for reducing the endless web of object dependencies (see http://www.cs.sandia.gov/~rabartl/readingList.html).
I would like to see an example of a design with shared object ownership that is ?much less complex? than all other functionally equivalent designs that eschew shared ownership. I'm not saying such applications don't exist, but IME they are rare, and a concrete example would help to bring this whole discussion back to earth.
[Bartlett, Roscoe A]
One design where shared mutable objects likely makes things overall simpler and more natural is the PDE expression mechanism in the Sundance code (http://www.math.ttu.edu/~klong/Sundance/html/). If the clients all expect remote updates of shared objects, then things work great.
I'm sorry, your reference isn't specific enough for me to see the design piece you're referring to.
[Bartlett, Roscoe A] It would take a significant effort to pin-point exactly the approach being used in the Sundance expression mechanism and then to describe an alternative design that does not rely on the kind of object sharing. I think that this example in Sundance is the exception and not the rule. In general, I don't like lots for mutable shared objects for the same reasons that you state. However, there are cases where using shared mutable objects is the best design to solve a given problem. As for dynamic vs. stack-based programming, in summary, if you like the approach used in: http://www.trip.caam.rice.edu/software/rvldoc/rvl.html which tries to to make *every* object a stack-based object, then you and I can safely agree to disagree about classic OO vs. purely stack-based programming. Sorry I misinterpreted your position (or projected other peoples positions on you) about the runtime vs. compile-time polymorphism debate. Cheers, - Ross

At Fri, 4 Jun 2010 10:40:04 -0600, Bartlett, Roscoe A wrote:
I _will_ argue that OOP has costs to software complexity that are often under-appreciated.
[Bartlett, Roscoe A]
Any approach can be misused, including anything you might be trying to advocate (but I am not 100% sure what that is exactly).
Note that C++ was first designed in the early 80s primarily to support #2 object-oriented C++. I can see the reason for this bias for #3 and #4 given the nature of most of the Boost libraries and you can have that opinion if you would like.
I don't, so please read what I wrote and try to address that rather than to your preconceived notions of my ?bias.?
[Bartlett, Roscoe A]
I am not trying to put words in your mouth. I am just trying to pin down your argument/position. You can say that I have a "bias" for good software if you would like :-)
I'm not sure what that is supposed to mean. Isn't it safe to assume that everyone here has that bias?
Seriously, I am sorry if I misinterpreted you position with the position of others on the boost list who were arguing for a more template-heavy approach. I guess that you are arguing for more of a stack-based approach (see below).
No, I'm not arguing for any particular approach. I'm arguing that the tools we have in C++ are enough to effectively handle the problems you're trying to address with Teuchos. But I have to admit that I haven't read the whole paper so I may not know what all those problems are.
I would argue that the current accepted approaches to writing OO programs in C++ (i.e. #2 OO C++ features) make it too easy to create programs with undefined behavior (i.e. segfaults or worse) and as a result, the paranoia of undefined behavior in C++, memory leaks etc. lead to lots of bad programming practices and designs (see Section 1 in http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf).
Seriously? Are you saying that a disciplined application of shared_ptr, weak_ptr, new_shared, et. al when dynamic allocations are called for is insufficient for most OO programs?
[Bartlett, Roscoe A]
As argued in the Teuchos MM document
http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf
these boost classes are a good start but they are *not* sufficient to build a safe and effective memory management system that encapsulated *all* raw pointers. What is missing for single objects, as described in Section 5.14 and other sections, is the Teuchos::Ptr class. Without integrating a class like Ptr in with the reference-counted pointer classes, you can't build a safe and effective memory management system in C++ that comes closer to eliminating undefined behavior.
Yes, wrapping every pointer and adding debug-mode-only checks for invalid usage is one way to help programmers debug their code. In a strict sense, though, eliminating undefined behavior requires keeping the checks on in optimized code. By the way, I actually like undefined behavior. It creates a clear boundary between valid and invalid usage. If all usages have defined behaviors, then neither the compiler nor runtime tools are allowed to interfere with such usages to help the user detect bugs. So I'm not in favor of eliminating it, at least not without some other measure of what's illegal.
Also, as described in Section 5.9.2, separating weak_ptr from shared_ptr is not ideal from a design perspective since it reduces the generality and reusability of software (see the arguments for this in the document).
I see the arguments. They boil down to, “there are complex cases where weakness needs to be determined at runtime rather than compile time.” IMO any design that uses reference counting but can't statically establish a hierarchy at ownership is broken. Even if the program works today, it will, eventually, acquire a cycle that consists entirely of non-weak pointers, and leak. So that *shouldn't* be convenient to write, so it's a problem with Teuchos that it encourages such designs. The fact that shared_ptr and weak_ptr are different types is a feature, not a bug. Furthermore if you really-really-really-really need to do that with boost, it's easy enough to build a type that acts as you wish (e.g. boost::variant<shared_ptr<T>, weak_ptr<T> >).
Did you read at least the sections called out in the reduced table of contents given in the Preface? I think these sections should address all of these questions (and more).
I am afraid I haven't had time, and probably won't until the end of next week.
When runtime performance or other issues related to dynamic allocations and classic OO are not a problem, classic OO C++ programs using runtime polymorphism are typically superior to highly templated C++ programs using static polymorphism
Straw man; I'm not arguing for ?templated C++ programs using static polymorphism.?
[Bartlett, Roscoe A]
Okay, the runtime vs. compile-time polymorphism debate is another issue (but a very important one). Are you instead arguing for a static and stack-based approach to programming?
No, I'm arguing for a value-based approach. If you're strict about it, you even turn references into values using type erasure… although doing so is often unfortunately so tedious that I wouldn't insist on it. See http://stlab.adobe.com/wiki/index.php/Papers_and_Presentations, particularly * Classes that work video: http://my.adobe.acrobat.com/p53888531/ * Thread Safety pdf (expanded version of slides in “Classes that work”): http://stlab.adobe.com/wiki/index.php/Image:2008_09_11_thread_safety.pdf * A Possible Future of Software Development video: http://www.youtube.com/watch?v=4moyKUHApq4 -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David, I think we are converging ...
-----Original Message----- From: David Abrahams [mailto:dave@boostpro.com] Sent: Friday, June 04, 2010 12:06 PM To: Bartlett, Roscoe A Cc: boost@lists.boost.org Subject: Re: Review of a safer memory management approach for C++?
...
I am not trying to put words in your mouth. I am just trying to pin down your argument/position. You can say that I have a "bias" for good software if you would like :-)
I'm not sure what that is supposed to mean. Isn't it safe to assume that everyone here has that bias?
[Bartlett, Roscoe A] It was a joke (a bad one I guess).
As argued in the Teuchos MM document
http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf
these boost classes are a good start but they are *not* sufficient to build a safe and effective memory management system that encapsulated *all* raw pointers. What is missing for single objects, as described in Section 5.14 and other sections, is the Teuchos::Ptr class. Without integrating a class like Ptr in with the reference-counted pointer classes, you can't build a safe and effective memory management system in C++ that comes closer to eliminating undefined behavior.
Yes, wrapping every pointer and adding debug-mode-only checks for invalid usage is one way to help programmers debug their code. In a strict sense, though, eliminating undefined behavior requires keeping the checks on in optimized code.
[Bartlett, Roscoe A] We are getting at some core issues here. In order to eliminate bad undefined behavior in production code using the Teuchos MM approach, we have to assume the following: 1) Very good unit, integration, and other verification tests exist such that if they pass with flying colors in a debug-mode build, then the behavior of the software is shown to be likely free from undefined memory usage behavior. 2) Validation of user input is left in the production code in major system boundaries to ensure that preconditions are not being violated. Therefore, if the code gets past accepting the user input, there is a very high probability that it will run free of undefined behavior (i.e. that can cause segfaults if we are luckly but cause much worse if we are unlucky). If #1 and #2 are satisfied, then there is a very high probability that the non-debug optimized version of the code will also be free of undefined memory usage behavior. The Teuchos MM classes are designed such that valid programs without debug-mode checking turned on will also be valid programs when debug-mode checking is turned on. This is key.
By the way, I actually like undefined behavior. It creates a clear boundary between valid and invalid usage. If all usages have defined behaviors, then neither the compiler nor runtime tools are allowed to interfere with such usages to help the user detect bugs. So I'm not in favor of eliminating it, at least not without some other measure of what's illegal.
[Bartlett, Roscoe A] I am fine with the language allowing for undefined behavior. Clearly if you want the highest performance, you have to turn off array-bounds checking, for instance, which allows for undefined behavior. What I am not okay with is people writing programs that expose that undefined behavior, especially w.r.t. to usage of memory. Every computational scientist has had the experience of writing code that appeared to work just fine on their main development platform but when they took to over to another machine for a "production" run on a large (expensive) MPP to run on 1000 processors, it segfaulted after having run for 45 minutes and lost everything. This happens all the time with current CSE software written in C, C++, and even Fortran. This is bad on many levels. Are you suggesting that people should write programs that rely on undefined behavior or are you just saying that you don't think it should be eliminated from the language?
Also, as described in Section 5.9.2, separating weak_ptr from shared_ptr is not ideal from a design perspective since it reduces the generality and reusability of software (see the arguments for this in the document).
I see the arguments. They boil down to, “there are complex cases where weakness needs to be determined at runtime rather than compile time.” IMO any design that uses reference counting but can't statically establish a hierarchy at ownership is broken. Even if the program works today, it will, eventually, acquire a cycle that consists entirely of non-weak pointers, and leak. So that *shouldn't* be convenient to write, so it's a problem with Teuchos that it encourages such designs. The fact that shared_ptr and weak_ptr are different types is a feature, not a bug. Furthermore if you really-really-really-really need to do that with boost, it's easy enough to build a type that acts as you wish (e.g. boost::variant<shared_ptr<T>, weak_ptr<T> >).
[Bartlett, Roscoe A] I have concrete use cases where it is not obvious that an RCP should be strong or weak w.r.t to a single class. One use case is where a Thyra::VectorBase object points to its Thyra:VectorSpaceBase object but where the scalar product of the Thyra::VectorSpaceBase object can be defined to be a diagonal p.d. matrix with the diagonal being another Thyra::VectorBase object. This creates a circular dependency between the diagonal VectorBase and the VectorSpaceBase objects that can only be resolved by making the RCP pointing to the VSB object weak. This is an unusual use case that the developers of the VectorBase and VectorSpaceBase subclasses should not have to worry about or change their design to handled. There are other examples too that I can present in other Trilinos packages. This point is debatable but in having to chose between shared_ptr and weak_ptr you are injecting notions of memory management into the design of a class that in many cases is orthogonal to the purpose of the class. This technically violates the Single Responsibility Principle (SRP). By using RCP consistently, you don't violate SRP or the Open-Closed Principle (OCP). External collaborators make the final decision about strong or weak ownership. We can agree to disagree but I can show other examples where having the feature where RCP can be weak or strong (decided at runtime) solved a circular reference problem without damaging the cohesion of the classes involved or even requiring a single line of code be changed in those classes. You can't do that with shared_ptr and weak_ptr without something like boost::variant<shared_ptr<T>, weak_ptr<T> > (which would require changing the code that currently uses shared_ptr or weak_ptr).
Did you read at least the sections called out in the reduced table of contents given in the Preface? I think these sections should address all of these questions (and more).
I am afraid I haven't had time, and probably won't until the end of next week.
[Bartlett, Roscoe A] I would be grateful to get feedback on the material.
When runtime performance or other issues related to dynamic allocations and classic OO are not a problem, classic OO C++ programs using runtime polymorphism are typically superior to highly templated C++ programs using static polymorphism
Straw man; I'm not arguing for ?templated C++ programs using static polymorphism.?
[Bartlett, Roscoe A]
Okay, the runtime vs. compile-time polymorphism debate is another issue (but a very important one). Are you instead arguing for a static and stack-based approach to programming?
No, I'm arguing for a value-based approach. If you're strict about it, you even turn references into values using type erasure… although doing so is often unfortunately so tedious that I wouldn't insist on it.
[Bartlett, Roscoe A] Value types, by definition, involve deep copy which is a problem with large objects. If a type uses shallow copy semantics to avoid the deep copy then it is really no different than using shared_ptr or RCP. If you store raw references, you are just doing the same thing as using RCP and shared_ptr except that you have no safely mechanism to catch dangling references (which you do with weak RCPs, but not with always-strong shared_ptrs). Raw (or otherwise) dumb references are *not* the solution for addressing sharing of large objects in persisting associations (value types or not).
See http://stlab.adobe.com/wiki/index.php/Papers_and_Presentations, particularly
* Classes that work video: http://my.adobe.acrobat.com/p53888531/
* Thread Safety pdf (expanded version of slides in “Classes that work”):
http://stlab.adobe.com/wiki/index.php/Image:2008_09_11_thread_safety.pd f
* A Possible Future of Software Development video: http://www.youtube.com/watch?v=4moyKUHApq4
[Bartlett, Roscoe A] I will take a look at these references. Clearly sharing mutable objects is a problem in multi-threaded programs but most OO designs would push threading into lower-level functionally and therefore threading would not typically affect top-level software architecture (where OO is typically the best approach to glue everything together). Clearly stack-based approaches have less problems in threaded programs where the object is created, used, and destroyed just within a single thread. Cheers, - Ross

At Fri, 4 Jun 2010 13:46:53 -0600, Bartlett, Roscoe A wrote:
I am fine with the language allowing for undefined behavior. Clearly if you want the highest performance, you have to turn off array-bounds checking, for instance, which allows for undefined behavior. What I am not okay with is people writing programs that expose that undefined behavior, especially w.r.t. to usage of memory. Every computational scientist has had the experience of writing code that appeared to work just fine on their main development platform but when they took to over to another machine for a "production" run on a large (expensive) MPP to run on 1000 processors, it segfaulted after having run for 45 minutes and lost everything. This happens all the time with current CSE software written in C, C++, and even Fortran. This is bad on many levels.
Absolutely. People need better tools for detecting memory usage errors. I would rather rely on tools like purify than build this sort of thing into a library dependency, but others' mileage may vary.
Are you suggesting that people should write programs that rely on undefined behavior
of course not!
or are you just saying that you don't think it should be eliminated from the language?
neither from the language nor from libraries (in the same sense as the language has undefined behavior, i.e. making certain usages illegal).
Also, as described in Section 5.9.2, separating weak_ptr from shared_ptr is not ideal from a design perspective since it reduces the generality and reusability of software (see the arguments for this in the document).
I see the arguments. They boil down to, “there are complex cases where weakness needs to be determined at runtime rather than compile time.” IMO any design that uses reference counting but can't statically establish a hierarchy at ownership is broken. Even if the program works today, it will, eventually, acquire a cycle that consists entirely of non-weak pointers, and leak. So that *shouldn't* be convenient to write, so it's a problem with Teuchos that it encourages such designs. The fact that shared_ptr and weak_ptr are different types is a feature, not a bug. Furthermore if you really-really-really-really need to do that with boost, it's easy enough to build a type that acts as you wish (e.g. boost::variant<shared_ptr<T>, weak_ptr<T> >).
[Bartlett, Roscoe A]
I have concrete use cases where it is not obvious that an RCP should be strong or weak w.r.t to a single class.
I don't know what “with respect to” means in this context. But the fact that you have come up with an example or two doesn't make this a good idea.
One use case is where a Thyra::VectorBase object points to its Thyra:VectorSpaceBase object but where the scalar product of the Thyra::VectorSpaceBase object can be defined to be a diagonal p.d. matrix with the diagonal being another Thyra::VectorBase object. This creates a circular dependency between the diagonal VectorBase and the VectorSpaceBase objects that can only be resolved by making the RCP pointing to the VSB object weak.
That's not clear enough to guess at the structure you're describing.
This is an unusual use case
And thus, doesn't need to be convenient.
that the developers of the VectorBase and VectorSpaceBase subclasses should not have to worry about or change their design to handled. There are other examples too that I can present in other Trilinos packages.
I realize that it must seem arrogant of me to say so without seeing the actual code, but IMO all these examples are very likely indicative of a design flaw.
This point is debatable but in having to chose between shared_ptr and weak_ptr you are injecting notions of memory management into the design of a class that in many cases is orthogonal to the purpose of the class.
Ownership, lifetime, whole/part relationships, and invariants are all very important elements of a class design, and the choice of shared-vs-weak is a reflection of those properties.
This technically violates the Single Responsibility Principle (SRP).
I guess I just disagree about that.
By using RCP consistently, you don't violate SRP or the Open-Closed Principle (OCP). External collaborators make the final decision about strong or weak ownership.
We can agree to disagree but I can show other examples where having the feature where RCP can be weak or strong (decided at runtime) solved a circular reference problem without damaging the cohesion of the classes involved or even requiring a single line of code be changed in those classes.
Again, that doesn't make it a good idea. If you make all your data members public, you can solve all sorts of problems by monkeypatching without changing a single line of the enclosing classes. The fact that shared_ptr/weak_ptr are different types makes it possible to statically ensure that you don't have cycles, just like private members make it possible to statically ensure that class invariants are not violated by clients.
Are you instead arguing for a static and stack-based approach to programming?
No, I'm arguing for a value-based approach. If you're strict about it, you even turn references into values using type erasure… although doing so is often unfortunately so tedious that I wouldn't insist on it.
Value types, by definition, involve deep copy which is a problem with large objects. If a type uses shallow copy semantics to avoid the deep copy then it is really no different than using shared_ptr or RCP.
The whole notion of deep-vs-shallow copy is a fallacy. If you nail down what it means to make a “copy”---which will force you to nail down what constitutes an object's value---you'll see that. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David,
-----Original Message----- From: David Abrahams [mailto:dave@boostpro.com] Sent: Friday, June 04, 2010 4:47 PM To: Bartlett, Roscoe A Cc: boost@lists.boost.org Subject: Re: Review of a safer memory management approach for C++?
At Fri, 4 Jun 2010 13:46:53 -0600, Bartlett, Roscoe A wrote:
I am fine with the language allowing for undefined behavior. Clearly if you want the highest performance, you have to turn off array-bounds checking, for instance, which allows for undefined behavior. What I am not okay with is people writing programs that expose that undefined behavior, especially w.r.t. to usage of memory. Every computational scientist has had the experience of writing code that appeared to work just fine on their main development platform but when they took to over to another machine for a "production" run on a large (expensive) MPP to run on 1000 processors, it segfaulted after having run for 45 minutes and lost everything. This happens all the time with current CSE software written in C, C++, and even Fortran. This is bad on many levels.
Absolutely. People need better tools for detecting memory usage errors. I would rather rely on tools like purify than build this sort of thing into a library dependency, but others' mileage may vary.
[Bartlett, Roscoe A] As described in Section 3.2 in the Teuchos MM report: http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf tools like valgrind and purify miss too many errors to be sufficient to solely rely on and will *never* catch semantic memory usage errors. Also, as described in Section 5.11.5, tools like valgrind and purify are far too expensive to run on anything but the smallest toy problem. Therefore, no-one will ever run these tools as part of (pre- or post-push) CI testing. If your memory problem only manifests itself on a larger problem, there is no way you can run valgrind or purify on the code. With Teuchos MM approach, the overhead is typically less than a factor of 10 (sometimes much less). Therefore, you can almost always afford to run a debug-mode build using the Teuchos MM classes, even on the largest problems. The Teuchos MM debug-mode is so cheap, that it is actually built into the Trilinos pre-push testing process that every developer runs before pushing to the main repo. Try that with valgrind and purify.
Value types, by definition, involve deep copy which is a problem with large objects. If a type uses shallow copy semantics to avoid the deep copy then it is really no different than using shared_ptr or RCP.
The whole notion of deep-vs-shallow copy is a fallacy. If you nail down what it means to make a “copy”---which will force you to nail down what constitutes an object's value---you'll see that.
[Bartlett, Roscoe A] It is not a fallacy. The only obvious behavior for value semantics is deep copy such that if you do: A a(...); B b = a; then any change to 'b' will have *no* impact on the behavior of 'a' at all, period. Anything else is not value semantics and will confuse people. Make it simple; most types should have either value semantics or reference semantics as described in Section 4.1 in the Teuchos MM report. Anything in between is just confusing and counter-intuitive. - Ross

on Fri Jun 04 2010, "Bartlett, Roscoe A" <rabartl-AT-sandia.gov> wrote:
David,
From: David Abrahams [mailto:dave@boostpro.com]
The whole notion of deep-vs-shallow copy is a fallacy. If you nail down what it means to make a “copy”---which will force you to nail down what constitutes an object's value---you'll see that.
[Bartlett, Roscoe A]
It is not a fallacy. The only obvious behavior for value semantics is deep copy such that if you do:
A a(...); B b = a;
then any change to 'b' will have *no* impact on the behavior of 'a' at all, period.
And that, right there, is the definition of “copy” (assuming A and B are the same type). There's no such thing as a “shallow copy” of a that's different from what you just described.
Anything else is not value semantics and will confuse people. Make it simple; most types should have either value semantics or reference semantics as described in Section 4.1 in the Teuchos MM report. Anything in between is just confusing and counter-intuitive.
Your “reference types” as defined there are not copyable, so there's no “shallow copy” going on. Period. Viewing it that way makes it *really* simple. :-) Cheers, -- Dave Abrahams BoostPro Computing http://boostpro.com

Bartlett, Roscoe A wrote:
I am fine with the language allowing for undefined behavior. Clearly if you want the highest performance, you have to turn off array-bounds checking, for instance, which allows for undefined behavior. What I am not okay with is people writing programs that expose that undefined behavior, especially w.r.t. to usage of memory. Every computational scientist has had the experience of writing code that appeared to work just fine on their main development platform but when they took to over to another machine for a "production" run on a large (expensive) MPP to run on 1000 processors, it segfaulted after having run for 45 minutes and lost everything. This happens all the time with current CSE software written in C, C++, and even Fortran. This is bad on many levels.
Are you suggesting that people should write programs that rely on undefined behavior or are you just saying that you don't think it should be eliminated from the language?
From my experience, the best tool to identify and eliminate undefined behaviour due to bad memory access is valgrind. I found it more practical and catching more errors than any library-level tool.

On 4 June 2010 12:40, Bartlett, Roscoe A <rabartl@sandia.gov> wrote:
Okay, the runtime vs. compile-time polymorphism debate is another issue (but a very important one).
You can split the polymorphism situations into 2 types: 1) Those with consistent types and 2) Those that require different types For (2), runtime polymorphism doesn't work. For (1), it can be coded as static polymorphism, then a single type erasure layer can be easily written (with an abstract base "interface" class and an "implementation" class template) that adapts the static classes for dynamic polymorphism. So why wouldn't I just write everything as static, then apply the erasure layer at the level appropriate for my application, if needed?
participants (4)
-
Bartlett, Roscoe A
-
David Abrahams
-
Mathias Gaunard
-
Scott McMurray