Search code examples
c++c++11thisshared-ptrenable-shared-from-this

Initializing a member with type std::shared_ptr<B> of a class A with a pointer to class B in a member function of B


I have the following code:

class Cohomology;

struct EMField
{
     std::shared_ptr<Cohomology> coh;
     std::array<DIM> data;

     // other methods

}

class Cohomology
{
     private:
        // private members
     public:
        Cohomology(PList params)
        {
             // Constructor of the class
        }
        
        virtual ~Cohomology() {std::cout << "Cohomology destroyed" << std::endl;}

        void initializeField(EMField& field)
        {
             field.coh.reset(this);
             // other methods to initialize field.data using the private members
        }
}

But the class Cohomology also has virtual methods that are implemented by SubCohomology:

class SubCohomology : public Cohomology
{
     public:
        SubCohomology(PList params) {}
        
        ~Cohomology() {std::cout << "SubCohomology destroyed" << std::endl;}

        // Implementation of the virtual methods
}

So a test code to check whether EMFields are initialized and can be manipulated looks like:

int main(int argc, char *argv[])
{
     // variables needed to initialize PList params
     PList params(); // construct params

     SubCohomology coh(params);

     EMField field;

     coh.initializeField(field);

}

The code compiles, but running it yields this error:

SubCohomology destroyed
Cohomology destroyed
free(): invalid pointer
[machine:324808] *** Process received signal ***
[machine:324808] Signal: Aborted (6)
[machine:324808] Associated errno: Unknown error 32767 (32767)
[machine:324808] Signal code:  (24)
[machine:324808] [ 0] /usr/lib/libc.so.6(+0x38a40)[0x7f4ac0054a40]
[machine:324808] [ 1] /usr/lib/libc.so.6(+0x884dc)[0x7f4ac00a44dc]
[machine:324808] [ 2] /usr/lib/libc.so.6(gsignal+0x18)[0x7f4ac0054998]
[machine:324808] [ 3] /usr/lib/libc.so.6(abort+0xd7)[0x7f4ac003e53d]
[machine:324808] [ 4] /usr/lib/libc.so.6(+0x7c67e)[0x7f4ac009867e]
[machine:324808] [ 5] /usr/lib/libc.so.6(+0x9226c)[0x7f4ac00ae26c]
[machine:324808] [ 6] /usr/lib/libc.so.6(+0x940bc)[0x7f4ac00b00bc]
[machine:324808] [ 7] /usr/lib/libc.so.6(__libc_free+0x73)[0x7f4ac00b2a33]
[machine:324808] [ 8] /home/user/builddir/test_fields(_ZN13EMFieldILi0ELi1EED2Ev+0x83)[0x556db1fc0f73]
[machine:324808] [ 9] /home/user/builddir/test_fields(main+0x36e)[0x556db1fa205e]
[machine:324808] [10] /usr/lib/libc.so.6(+0x232d0)[0x7f4ac003f2d0]
[machine:324808] [11] /usr/lib/libc.so.6(__libc_start_main+0x8a)[0x7f4ac003f38a]
[machine:324808] [12] /home/user/builddir/test_fields(_start+0x25)[0x556db1fa3ba5]
[machine:324808] *** End of error message ***
Aborted (core dumped)

which happens after the function initializeField. It is a memory problem, which might be related to trying to free() a non-existing resource.

I suspect that using std::enable_shared_from_this might be helpful to address this problem but I don't know how to implement the mandatory inheritance considering my particular problem, as I am trying to initialize the std::shared_ptr<Cohomology> coh class member of a field in the Cohomology class.

The example outlined here is very helpful to understand how to use this, but I don't know if I would have to nest another struct in EMField to implement this. I also understand the problem solved in this question: when should we use std::enable_shared_from_this, but I cannot put it in the context where a struct has a std::shared_ptr as a member.

Please understand that many EMField objects might be added, whose std::shared_ptr<Cohomology> member points for all fields to the same object

Thank you.


Solution

  • std::shared_ptr exists to manage the lifetime of dynamically-allocated objects. No such management is needed (or possible) for an object with automatic storage duration (like coh). Its lifetime is tied to its enclosing scope. Therefore a pointer to coh must never be managed by a std::shared_ptr.

    Instead, you should consider creating a constructor in EMField that accepts a std::shared_ptr<Cohomology> and having the caller create an appropriate dynamically-allocated object:

    struct EMField
    {
        std::shared_ptr<Cohomology> coh;
        // ...
    
        EMField(std::shared_ptr<Cohomology> c)
            : coh{c}
        {}
    
        // ...
    };
    
    int main(int argc, char *argv[])
    {
        // variables needed to initialize PList params
        PList params(); // construct params
    
        auto coh = std::make_shared<SubCohomology>(params);
    
        EMField field(coh);
    
        // No longer needed since EMField's constructor initializes its
        // fields now
        // coh.initializeField(field);
    }
    

    Demo


    If you absolutely don't want to have to pass your Cohomology object into EMField from the caller, and all Cohomology objects should be dynamically-allocated, and they should all be managed by std::shared_ptrs, then, and only then, is std::enable_shared_from_this the tool for the job.

    Example:

    class Cohomology : public std::enable_shared_from_this<Cohomology>
    {
        private:
            // private members
        protected:
            Cohomology(PList params)
            {
                 // Constructor of the class
            }
    
            Cohomology(const Cohomology&) = delete;
    
        public:
            virtual ~Cohomology()
            {
                std::cout << "Cohomology destroyed\n";
            }
    
            static std::shared_ptr<Cohomology> create(PList params)
            {
                return std::shared_ptr<Cohomology>(new Cohomology(params));
            }
    
            void initializeField(EMField& field)
            {
                 field.coh = shared_from_this();
                 // ...
            }
    
            // ...
    };
    
    class SubCohomology : public Cohomology
    {
        private:
            SubCohomology(PList params)
                : Cohomology(params)
            {}
    
        public:
            ~SubCohomology()
            {
                std::cout << "SubCohomology destroyed\n";
            }
            
            static std::shared_ptr<SubCohomology> create(PList params)
            {
                return std::shared_ptr<SubCohomology>(new SubCohomology(params));
            }
    
            // Implementation of the virtual methods
    };
    
    int main(int argc, char *argv[])
    {
        // variables needed to initialize PList params
        PList params; // construct params
    
        std::shared_ptr<SubCohomology> coh = SubCohomology::create(params);
    
        EMField field;
    
        coh->initializeField(field);
    }
    

    Demo

    Note that Cohomology and SubCohomology now have non-public constructors. If you inherit from std::enable_shared_from_this you should not allow any objects to ever not be managed by a std::shared_ptr, so separate factory functions are needed to ensure that fact.