0

Recently, I came across an example that my destructor needs to take a parameter.

I am working on a C package that manages memory internally and keeps track of allocating and freeing with parameters of its own. I do not want to break that.

I wrote a C code, that initializes my own data structures and frees them at the end. When I decided to migrate to C++ I realized that allocating data structures and freeing them should be placed in constructor and destructors. So I changed those functions to constructor and destructor. My problem now is that I need to use a parameter to pass to destructor to free allocated data; that parameter is not in my own code and is C and I do not want to mess with that.

My question is twofold:

Why C++ decided not to accept parameters in destructors in the first place? and What are my options? (I can save a pointer to that parameter or somehow call that function in my destructor, but it seems not a good practice of C++ programming)

Update: adding some code Here is my imaginary class:

class paru_symbolic/* paru_symbolic*/
{
    public:
        paru_symbolic ( cholmod_sparse *A, cholmod_common *cc ); // constructor
        ~paru_symbolic (cholmod_common *cc ); // destructor

        // -------------------------------------------------------------------------
        // row-form of the input matrix and its permutations
        // -----------------------------------------------------------------------
              //--- other stuff
                     ...
};

Here is my current C constructor:

#include "Parallel_LU.hpp"
paru_symbolic *paru_sym_analyse
(
 // inputs, not modified
   cholmod_sparse *A,
 // workspace and parameters
   cholmod_common *cc ){   

    DEBUGLEVEL(0);
...
   aParent = (Int*) paru_alloc (m+nf, sizeof(Int),cc);

...
}

and destructor:

void paru_freesym (paru_symbolic **LUsym_handle,
            // workspace and parameters
    cholmod_common *cc
){
    DEBUGLEVEL (0);
    if (LUsym_handle == NULL || *LUsym_handle == NULL){
        // nothing to do; caller probably ran out of memory
        return;
    }

    paru_symbolic *LUsym;
    LUsym = *LUsym_handle;

    Int m, n, anz, nf, rjsize; 
...
    cholmod_l_free (m+nf, sizeof (Int), LUsym->aParent, cc);

...
    cholmod_l_free (1, sizeof (paru_symbolic), LUsym, cc);

   *LUsym_handle = NULL;
}

The parameter cc is used inside the SuiteSparse package for tracking allocations and freeing data. It has been used all over in SuiteSparse package and is a useful tool for tracking memory. Some people mentioned that who wants to pass a parameter to the destructor.That is a fair point, but we could have the same parameters that we have in the constructor as a default.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Aznaveh
  • 558
  • 8
  • 27
  • 1
    Possible duplicate of [Destructor parameters](https://stackoverflow.com/questions/6245295/destructor-parameters) – The Bearded Llama Jul 04 '17 at 15:37
  • Classes are supposed to be self-contained, and interfaces are supposed to be relatively narrow. If a class contains data which needs to be managed (for example, that has to be freed at destruct time), the class should know everything about it that data. It's hard to imagine any data that needs to be passed to the destructor, that the class shouldn't know internally (that is, in member variables) already. What's your example of parameters you need to pass to the destructor? – Steve Summit Jul 04 '17 at 15:37
  • How would you pass the parameters? – drescherjm Jul 04 '17 at 15:37
  • I already have seen the other post about destructor passing parameters. It did not have a good example in it – Aznaveh Jul 04 '17 at 15:40
  • Destructor gets called on an object when it goes out of scope, so how you call it with params? – hungryWolf Jul 04 '17 at 15:42
  • If you show us the code, may be we can suggest something? – hungryWolf Jul 04 '17 at 15:43
  • 1
    Maybe the OP can open a new question about what he is trying to do with the parameter in the destructor. Perhaps there is a possible way to accomplish this task without needing functionality that the language does not provide. – drescherjm Jul 04 '17 at 15:44
  • @drescherjm agreed – hungryWolf Jul 04 '17 at 15:45
  • 2
    How about showing a [mcve] with the use case you have in mind. Right now this is an [XY question](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). You have a problem X, you ask about what *you think* is the solution Y. Let me assure you that it isn't Y, but we can't show you how to solve X without seeing more details. An abstract description is not good enough. – StoryTeller - Unslander Monica Jul 04 '17 at 15:46
  • My code has too many details and I do not have a working version of C++ yet. I am working on SuiteSparse https://github.com/Aznaveh/SuiteSparse_v4.5.5 for allocating memory there is a parameter cc in all the package. I use it for allocating and freeing – Aznaveh Jul 04 '17 at 15:48
  • 1
    I did not ask for your code, I asked for a [mcve]. Do follow links in comments, please. – StoryTeller - Unslander Monica Jul 04 '17 at 15:49
  • 1
    @StoryTeller that is a very good idea. I am trying to delete those part out of it and post the code here – Aznaveh Jul 04 '17 at 15:51
  • @Aznaveh the destructor is called automatically. Who should pass which parameter to it? – Jabberwocky Jul 04 '17 at 15:53
  • 1
    *"but we could have the same parameters that we have in the constructor as a default."* - You can have an unbound number of c'tors. Tracking which one was used to create which object and with which arguments is unacceptable overhead. – StoryTeller - Unslander Monica Jul 04 '17 at 16:48
  • Don't spam tags! This is clearly not C. – too honest for this site Jul 04 '17 at 17:32

3 Answers3

14

Parameters in destructors do not make sense as destructors are automatically invoked when an object goes out of scope. How could you pass a parameter to that?

{ 
   Foo x;
} // `~Foo()` is automatically called here

What you probably want is to store the resource in your class. Here's an unrealistic example with a dynamically-allocated pointer:

struct RAIIPointer
{
    Foo* ptr;

    // Initialize the resource
    RAIIPointer() : ptr{new Foo{/*...*/}}
    {
    }

    RAIIPointer(const RAIIPointer&) = delete;
    RAIIPointer& operator=(const RAIIPointer&) = delete;

    RAIIPointer(RAIIPointer&&) { /* implement appropriately */ }
    RAIIPointer& operator=(RAIIPointer&&) { /* implement appropriately */ }

    // Release the resource
    ~RAIIPointer() 
    {
        delete ptr;
    }
};

Note that in a real-world scenario you would use std::unique_ptr, and if you were implementing your own RAII resource class you would need to implement proper copy/move operations.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • I understand that this is an imprecise answer *(e.g. you can call destructors manually, if you want to)*, but I tried to tailor it to the OP's experience level. – Vittorio Romeo Jul 04 '17 at 15:40
  • That is the point; Resource r is not my own resource. I borrowed it from some other code. I can add a pointer to it in my data structure as I said, but it does not seem correct – Aznaveh Jul 04 '17 at 15:42
  • 6
    @Aznaveh Why does that not seem correct? That's the perfectly valid way to build a C++ wrapper around a C interface. Maybe you should show some sample code... – king_nak Jul 04 '17 at 15:44
  • @Aznaveh yup, that's what you do in cpp – hungryWolf Jul 04 '17 at 15:46
  • 2
    @Aznaveh - Well that's how we roll. We even have standard utilities for it, like `std::unique_ptr`. Resource ownership is not an unsolved problem in C++. – StoryTeller - Unslander Monica Jul 04 '17 at 16:00
  • @juanchopanza: I mentioned it in the answer, but I made some more changes to make it obvious. – Vittorio Romeo Jul 04 '17 at 16:24
2

@Vittorio's answer has the general approach. I'll just tailor it to your added sample code.

The way you presented the issue is as follows:

  1. You use a utility type cholmod_common to do some useful work for your type.
  2. The instance of that type can be passed to several paru_symbolic objects you create.
  3. No single instance of paru_symbolic owns it uniquely, but they all depend on it at the start and end of their lifetime. It must exist for the same duration as all of those objects.

That screams std::shared_ptr to me.

class paru_symbolic
{
    std::shared_ptr<cholmod_common> _cc;
    public:
        paru_symbolic ( cholmod_sparse *A, std::shared_ptr<cholmod_common> cc)
         : _cc(cc) {
           // constructor
         } 
        ~paru_symbolic () {
          // use _cc , it's guaranteed to live until the closing brace at the least.
        }
};

That's it. The ownership is now shared. The last object that depends on the instance of cholmod_common will be the one to clean it up with no extra work from you.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I think this is the answer I need. However, cc is not initialized in my constructor. I borrowed it from somewhere else and I pass it to the constructor. I can design it differently though – Aznaveh Jul 04 '17 at 17:11
  • 1
    @Aznaveh - If you can redesign it, that's good. The main point is that there should never be a raw `cholmod_common` pointer. Instead, it should be created into a `std::shared_ptr` immediately, before *that* shared_ptr is passed to the constructors of dependent classes to establish shared ownership. – StoryTeller - Unslander Monica Jul 04 '17 at 17:17
0

In C++, this sort of thing is done via an allocator, see also std::allocator. So, you could create your own allocator with a cholmod_common* data member.

Alternatively, you can use the deleter parameter for memory management via smart pointers. For example

class foo
{
  struct bar_deleter {
    cholmod_common *cc;
    bar_deleter(cholmod_common *c) : cc(c) {}
    void destroy(bar*) { cc->free(bar); }
  };
  std::unique_ptr<bar,bar_deleter> data;
public:
  foo(some_type const&input, cholmod_common *cc)
    : data(cc->create<bar>(input), bar_deleter(cc)) {}
};

Which, of course, also keeps a cholmod_common* with the data.

The name "cholmod_common" suggests that this is a common resource, i.e. there is only one cholmod_common object at any given time. Iff this is so, then you could use a stateless allocator, which keeps the common resource as a static data member (or obtains it via a singleton).

Walter
  • 44,150
  • 20
  • 113
  • 196