From aae3ab83d2b647a9d5a9677e6cabbf04ff598840 Mon Sep 17 00:00:00 2001 From: Daniel Arndt Date: Sat, 3 Nov 2018 00:17:13 +0100 Subject: [PATCH] Update description of the Subscriptor-SmartPointer pair in the examples --- examples/step-13/step-13.cc | 7 +--- examples/step-37/step-37.cc | 4 +- examples/step-6/doc/results.dox | 49 ------------------------ examples/step-6/step-6.cc | 67 --------------------------------- examples/step-60/step-60.cc | 21 ----------- examples/step-7/step-7.cc | 60 ++++++++++------------------- 6 files changed, 24 insertions(+), 184 deletions(-) diff --git a/examples/step-13/step-13.cc b/examples/step-13/step-13.cc index 3e4fa29104..b37aac8107 100644 --- a/examples/step-13/step-13.cc +++ b/examples/step-13/step-13.cc @@ -476,12 +476,9 @@ namespace Step13 // therefore just declare such an abstract base class, taking a pointer to // a triangulation in the constructor and storing it henceforth. Since // this triangulation will be used throughout all computations, we have to - // make sure that the triangulation exists until the destructor exits. We + // make sure that the triangulation is valid until it is last used. We // do this by keeping a SmartPointer to this triangulation, - // which uses a counter in the triangulation class to denote the fact that - // there is still an object out there using this triangulation, thus - // leading to an abort in case the triangulation is attempted to be - // destroyed while this object still uses it. + // as explained in step-7. // // Note that while the pointer itself is declared constant // (i.e. throughout the lifetime of this object, the pointer points to the diff --git a/examples/step-37/step-37.cc b/examples/step-37/step-37.cc index facd794e4f..65d118b59b 100644 --- a/examples/step-37/step-37.cc +++ b/examples/step-37/step-37.cc @@ -254,8 +254,8 @@ namespace Step37 // This is the constructor of the @p LaplaceOperator class. All it does is // to call the default constructor of the base class // MatrixFreeOperators::Base, which in turn is based on the Subscriptor - // class that asserts that this class cannot go out of scope while still in - // use in e.g. a preconditioner. + // class that asserts that this class is not accessed after going out of scope + // e.g. in a preconditioner. template LaplaceOperator::LaplaceOperator() : MatrixFreeOperators::BaseStep6 class is omitted -from this example: - -~~~{} -An error occurred in line <104> of file <.../source/base/subscriptor.cc> in function - void dealii::Subscriptor::check_no_subscribers() const -The violated condition was: - counter == 0 -Additional information: - Object of class N6dealii15SparsityPatternE is still used by 1 other objects. - -(Additional information: - from Subscriber SparseMatrix) - -See the entry in the Frequently Asked Questions of deal.II (linked to from -http://www.dealii.org/) for a lot more information on what this error means and -how to fix programs in which it happens. - -Stacktrace: ------------ -#0 .../lib/libdeal_II.g.so.9.0.0: dealii::Subscriptor::check_no_subscribers() const noexcept -# .../lib/libdeal_II.g.so.9.0.0: dealii::Subscriptor::~Subscriptor() -#2 ./step-6: Step6<2>::~Step6() -#3 ./step-6: main --------------------------------------------------------- -~~~ - -From the above error message, we conclude that something is still using an -object with type N6dealii15SparsityPatternE. This is of course the "mangled" name for -SparsityPattern. The mangling works as follows: the N6 indicates -a namespace with six characters (i.e., dealii) and the -15 indicates the number of characters of the template class (i.e., -SparsityPattern). - -From this we can already glean a little bit who is the culprit here, and who the -victim: The one object that still uses the SparsityPattern is the -SparseMatrix. - -The stacktrace gives an indication of where the problem happened. We -see that the AssertNothrow macro was triggered in the -destructor of the SparseMatrix class (which inherits from Subscriptor) that was called -through a few more functions from the destructor of the -Step6 class, exactly where we have commented out -the call to SparseMatrix::clear(). - - -

Possibilities for extensions

diff --git a/examples/step-6/step-6.cc b/examples/step-6/step-6.cc index aac02d826a..b45caaf7bd 100644 --- a/examples/step-6/step-6.cc +++ b/examples/step-6/step-6.cc @@ -96,7 +96,6 @@ class Step6 { public: Step6(); - ~Step6(); void run(); @@ -118,9 +117,6 @@ private: // conditions. AffineConstraints constraints; - // The sparsity pattern and sparse matrix are deliberately declared in the - // opposite of the order used in step-2 through step-5 to demonstrate the - // primary use of the Subscriptor and SmartPointer classes. SparseMatrix system_matrix; SparsityPattern sparsity_pattern; @@ -159,69 +155,6 @@ Step6::Step6() {} -// @sect4{Step6::~Step6} - -// Here comes the added destructor of the class. Some objects in deal.II store -// pointers to other objects: in particular a SparseMatrix stores a SmartPointer -// pointing to the SparsityPattern with which it was initialized. This example -// deliberately declares the SparseMatrix before the SparsityPattern to make -// this dependency clearer. Of course we could have left this order unchanged, -// but we would like to show what happens if the order is reversed since this -// produces a rather nasty side-effect and results in an error which is -// difficult to track down if one does not know what happens. -// -// What happens is the following: when we initialize a SparseMatrix, -// the matrix stores a pointer to the provided SparsityPattern instead of -// copying it. Since this pointer is used until either another -// SparsityPattern is attached or the SparseMatrix is destructed, it would be -// unwise to allow the SparsityPattern to be destructed before the -// SparseMatrix. To disallow this, the SparseMatrix increases a counter inside -// the SparsityPattern which counts how many objects use it (this is what the -// Subscriptor/SmartPointer class pair is used for, -// in case you want something like this for your own programs; see step-7 for -// a more complete discussion of this topic). If we try to destroy the object -// while the counter is larger than zero then the program will either abort -// (the default) or print an error message and continue: see the documentation -// of AssertNothrow for more details. In either case the program contains a -// bug and this facility will, hopefully, point out where. -// -// To be fair, such errors due to object dependencies are not particularly -// popular among programmers using deal.II, since they only tell us that -// something is wrong, namely that some other object is still using the -// object that is presently being destroyed, but most of the time not -// which object is still using it. It is therefore often rather -// time-consuming to find out where the problem exactly is, although it is -// then usually straightforward to remedy the situation. However, we believe -// that the effort to find invalid pointers to objects that no longer exist is -// less if the problem is detected once the pointer becomes invalid, rather -// than when non-existent objects are actually accessed again, since then -// usually only invalid data is accessed, but no error is immediately raised. -// -// Coming back to the present situation, if we did not write this destructor, -// then the compiler would generate code that triggers exactly the behavior -// described above. The reason is that member variables of the -// Step6 class are destroyed bottom-up (i.e., in reverse order of -// their declaration in the class), as always in C++. Thus, the -// SparsityPattern will be destroyed before the SparseMatrix, since its -// declaration is below the declaration of the sparsity pattern. This triggers -// the situation above, and without manual intervention the program will abort -// when the SparsityPattern is destroyed. What needs to be done is to -// tell the SparseMatrix to release its pointer to the SparsityPattern. Of -// course, the SparseMatrix will only release its pointer if it really does -// not need the SparsityPattern any more. For this purpose, the SparseMatrix -// class has a function SparseMatrix::clear() which resets the object to its -// default-constructed state by deleting all data and resetting its pointer to -// the SparsityPattern to 0. After this, you can safely destruct the -// SparsityPattern since its internal counter will be zero. -// -// We show the output of the other case (where we do not call -// SparseMatrix::clear()) in the results section below. -template -Step6::~Step6() -{ - system_matrix.clear(); -} - // @sect4{Step6::setup_system} diff --git a/examples/step-60/step-60.cc b/examples/step-60/step-60.cc index 7f6a93e521..ff958d9694 100644 --- a/examples/step-60/step-60.cc +++ b/examples/step-60/step-60.cc @@ -362,27 +362,6 @@ namespace Step60 ParameterAcceptorProxy> embedded_configuration_function; - - // The embedded mapping. Notice that the order in which we construct these - // unique pointers is important. They will be destroyed in reversed order, - // so it is important that we respect the dependency tree. In particular, - // the embedded mapping will depend on both the `embedded_dh` and the - // `embedded_configuration`. If we declare it after the above two, we are - // fine, otherwise we would have do release this pointer manually in the - // destructor, or we'd get an error like - // - // @code - // -------------------------------------------------------- - // An error occurred in line <104> of file <../source/base/subscriptor.cc> - // in function - // void dealii::Subscriptor::check_no_subscribers() const - // The violated condition was: - // counter == 0 - // Additional information: - // (none) - // @endcode - // - // at the end of the program. std::unique_ptr> embedded_mapping; // We do the same thing to specify the value of the function $g$, diff --git a/examples/step-7/step-7.cc b/examples/step-7/step-7.cc index c8bf1412ab..97c0e7daa9 100644 --- a/examples/step-7/step-7.cc +++ b/examples/step-7/step-7.cc @@ -373,16 +373,12 @@ namespace Step7 // beginning of the program or an outer loop, and they are destroyed at // the very end. The question is: can we guarantee that the two objects // which the DoFHandler uses, live at least as long as they are in use? - // This means that the DoFHandler must have some kind of lock on the - // destruction of the other objects, and it can only release this lock - // once it has cleared all active references to these objects. We have - // seen what happens if we violate this order of destruction in the - // previous example program: an exception is thrown that terminates the - // program in order to notify the programmer of this potentially dangerous - // state where an object is pointed to that no longer persists. + // This means that the DoFHandler must have some kind of knowledge on the + // destruction of the other objects. // // We will show here how the library managed to find out that there are - // still active references to an object. Basically, the method is along + // still active references to an object and the object is still alive + // frome the point of view of a using object. Basically, the method is along // the following line: all objects that are subject to such potentially // dangerous pointers are derived from a class called Subscriptor. For // example, the Triangulation, DoFHandler, and a base class of the @@ -392,40 +388,24 @@ namespace Step7 // a pointer to that object, we can increase its use counter, and when we // move away our pointer or do not need it any more, we decrease the // counter again. This way, we can always check how many objects still use - // that object. + // that object. Additionally, the class requires to know about a pointer + // that it can use to tell the subscribing object about its invalidation. // - // On the other hand, if an object of a class that is derived from the - // Subscriptor class is destroyed, it also has to call the destructor of - // the Subscriptor class. In this destructor, there will then be a check - // whether the counter is really zero. If yes, then there are no active - // references to this object any more, and we can safely destroy it. If - // the counter is non-zero, however, then the destruction would result in - // stale and thus potentially dangerous pointers, and we rather throw an - // exception to alert the programmer that this is doing something - // dangerous and the program better be fixed. + // If an object of a class that is derived from the Subscriptor class is + // destroyed, it also has to call the destructor of the Subscriptor class. + // In this destructor, we tell all the subscribing objects about the + // invalidation of the object using the stored pointers. The same happens + // when the object appears on the right hand side of a move expression, + // i.e., it will no longer contain valid content after the operation. The + // subscribing class is expected to check the value stored in its + // corresponding pointer before trying to access the object subscribed to. // - // While this certainly all sounds very well, it has some problems in - // terms of usability: what happens if I forget to increase the counter - // when I let a pointer point to such an object? And what happens if I - // forget to decrease it again? Note that this may lead to extremely - // difficult to find bugs, since the place where we have forgotten - // something may be far away from the place where the check for zeroness - // of the counter upon destruction actually fails. This kind of bug is - // rather annoying and usually very hard to fix. - // - // The solution to this problem is to again use some C++ trickery: we - // create a class that acts just like a pointer, i.e. can be dereferenced, - // can be assigned to and from other pointers, and so on. This can be done - // by overloading the several dereferencing operators of that - // class. Within the constructors, destructors, and assignment operators - // of that class, we can however also manage increasing or decreasing the - // use counters of the objects we point to. Objects of that class - // therefore can be used just like ordinary pointers to objects, but they - // also serve to change the use counters of those objects without the need - // for the programmer to do so herself. The class that actually does all - // this is called SmartPointer and takes as template parameter the data - // type of the object which it shall point to. The latter type may be any - // class, as long as it is derived from the Subscriptor class. + // This is exactly what the SmartPointer class is doing. It basically acts + // just like a pointer, i.e. it can be dereferenced, can be assigned to and + // from other pointers, and so on. On top of that it uses the mechanism + // described above to find out if the pointer this class is representing is + // dangling when we try to dereference it. In that case an exception is + // thrown. // // In the present example program, we want to protect the finite element // object from the situation that for some reason the finite element -- 2.39.5