0

I have a class. When this class is instantiated, I want the instance added to a list. When the object is deleted, I want it removed from the list.

So I give the object a shared pointer to itself. I then have a list of weak pointers to those shared pointers. When an object is created, it creates a shared pointer to itself, makes a weak pointer to that, and puts the weak pointer in a list.

When the object is destroyed, the shared pointer is as well. Whenever I try to access a member in the list, I ensure that it hasn't expired and that its use count isn't 0. Despite this, I still crash when the list member is destroyed. Why? Can I get around it? Here's my SSCCE:

#include <iostream>
#include <memory>
#include <vector>

class test
{
    private:
        std::shared_ptr<test> self;

    public:
        int val;
        test(int set);

        test(test &copy) = delete; // making sure there weren't issues 
                                   // with a wrong instance being deleted
};

std::vector<std::weak_ptr<test>> tests;

test::test(int set):
    val(set)
{
    this->self = std::shared_ptr<test>(this);

    tests.push_back(std::weak_ptr<test>(this->self));
}

void printTests()
{
    for (auto i = tests.begin(); i != tests.end(); i++)
    {
        if (i->use_count() == 0 || i->expired())
        {
            tests.erase(i);
            continue;
        }

        std::cout << i->lock()->val << std::endl;
    }

    std::cout << std::endl;
}

int main(int argc, char **argv)
{
    {
        test t(3);

        std::cout << "First tests printing: " << std::endl;

        printTests();
    } // SEGFAULTS HERE

    std::cout << "Second tests printing: " << std::endl;
    printTests();

    return 0;
}

The output of this program is as follows:

First tests printing:
3

Segmentation fault (core dumped)
Publius
  • 1,184
  • 2
  • 10
  • 27
  • 2
    I will virtually *guarantee* you that `this->self = std::shared_ptr(this);` is not doing what you think it is, especially since the actual object, `test t(3)`, is on the *stack*. – WhozCraig Feb 19 '13 at 01:27
  • That it's on the stack just means that test is destroyed at the end of its scope, which is what I want. How else am I going to do what I want to do? – Publius Feb 19 '13 at 01:28
  • 1
    What do you think `std::shared_ptr<>` does with the object pointer contained within when the shared ptr obj is destroyed? – WhozCraig Feb 19 '13 at 01:28
  • I think it destroys it. If that's a problem, how do I get around it? I need to be able to derive this class so I can't change what it's pointing to in the constructor. – Publius Feb 19 '13 at 01:29
  • 1
    That would be correct. See both answers below (there may be more by the time you get this). – WhozCraig Feb 19 '13 at 01:30
  • http://stackoverflow.com/a/3208981/103167 – Ben Voigt Feb 19 '13 at 01:35

3 Answers3

3

Your issue is with how you are creating the self pointer:

 this->self = std::shared_ptr<test>(this);

When a shared_ptr is created with this constructor, according to the documentation,

When T is not an array type, constructs a shared_ptr that owns the pointer p. ... p must be a pointer to an object that was allocated via a C++ new expression or be 0

So the issue is that the shared_ptr is taking ownership of your stack object, so when the object gets destructed (and the shared_ptr along with it), shared_ptr is trying to delete your object that is on the stack. This is not valid.

For your use case, if you expect tests to outlive your vector, then you might be able to just store this.

JaredC
  • 5,150
  • 1
  • 20
  • 45
  • Great. So how do I do what I need to do? I can't replace what self is pointing to in the destructor because I need to derive this object. – Publius Feb 19 '13 at 01:31
  • 1
    @Avi: Why not just have the destructor remove the list entry? – Ben Voigt Feb 19 '13 at 01:32
  • For the reasons I've said twice now: I need to be able to derive test, and I'd prefer not to have to rewrite that code for each derived object's destructor. That would defeat the purpose of what I'm trying to do here, which is hide the list from the coders of the derived objects. – Publius Feb 19 '13 at 01:33
  • @Avi the base class destructor is still called when the derived class is destructed. – JaredC Feb 19 '13 at 01:35
  • Yes, but from what I understand, the base class _destructor_ isn't. – Publius Feb 19 '13 at 01:35
  • @Avi: You don't need to. Destructors automatically destroy subobjects, including the base subobject. All base destructors are called, certainly. – Ben Voigt Feb 19 '13 at 01:35
  • Right, but they don't automatically change the place to which its shared pointer members are pointing, which is what I need to do. – Publius Feb 19 '13 at 01:36
  • you may need `shared_from_this()` then. – JaredC Feb 19 '13 at 01:37
  • 1
    @Avi: Huh? Look at http://stackoverflow.com/a/3208981/103167 Get rid of the shared pointers. Have a simple list of ordinary pointers (to the base class type), with insertion in base constructor and removal in base destructor. – Ben Voigt Feb 19 '13 at 01:37
  • Is the base destructor called from the child classes? I thought it wasn't. – Publius Feb 19 '13 at 01:38
  • @Ben his concern may be safely accessing the elements of the list when they can be destructed at any time. Just a wild guess though. – JaredC Feb 19 '13 at 01:38
  • @Jared: Then the destructor can overwrite the list entry with a NULL pointer, for later removal, instead of erasing the entry immediately. – Ben Voigt Feb 19 '13 at 01:38
  • And the base destructor will be called from child objects? I was under the impression it wouldn't be. – Publius Feb 19 '13 at 01:39
  • 1
    @Avi: You thought wrong. This is the third time you're being told that *every derived class destructor, including one implicitly generated by the compiler, calls base destructors as well as destructors for non-static data members* – Ben Voigt Feb 19 '13 at 01:40
  • Right, but that's not what I'm asking. I'm asking if a destructor of a child object calls the destructor of its parent. I'm not asking if child destructors destroy the members inherited from parents. – Publius Feb 19 '13 at 01:41
  • 1
    @Avi: Derived destructors don't destroy inherited members. They do destroy the base subobject, via a call to the base class destructor, and that destroys its own members. And also runs whatever code you put in the base destructor. – Ben Voigt Feb 19 '13 at 01:42
  • Okay, great, that's what I needed to no. I have no idea what gave me the impression otherwise. Thank you very much for your assistance. I even fixed the problem you gave me in your answer. – Publius Feb 19 '13 at 01:43
  • 1
    @Avi: Probably confusion between base/derived and parent/child. Parent/child often refers to containment, not inheritance. – Ben Voigt Feb 19 '13 at 01:43
2

I think the OP is interested in a solution to his original problem even if it uses a different method than the one he attempted. Here is a simple example of how to add an object to a global list when it is constructed, and remove it when it is deleted. One thing to remember: you must call AddList in every constructor you add to your base class. I didn't know whether you want the list to be accessible outside the class or not, so I added getter functions to return non-const iterators to the list.

class MyClass
{
private:
    static std::list<MyClass*> mylist;
    std::list<MyClass*>::iterator mylink;

    // disable copy constructor and assignment operator
    MyClass(const MyClass& other);
    MyClass& operator = (const MyClass& other);

    void AddList()
    {
        mylink = mylist.insert(mylist.end(), this);
    }

    void RemoveList()
    {
        mylist.erase(mylink);
    }

public:
    MyClass()
    {
        AddList();
    }

    virtual ~MyClass()
    {
        RemoveList();
    }

    static std::list<MyClass*>::iterator GetAllObjects_Begin()
    {
        return mylist.begin();
    }

    static std::list<MyClass*>::iterator GetAllObjects_End()
    {
        return mylist.end();
    }

    virtual std::string ToString() const
    {
        return "MyClass";
    }
};

class Derived : public MyClass
{
    virtual std::string ToString() const
    {
        return "Derived";
    }
};

std::list<MyClass*> MyClass::mylist;


int main()
{
    std::vector<MyClass*> objects;
    objects.push_back(new MyClass);
    objects.push_back(new MyClass);
    objects.push_back(new Derived);
    objects.push_back(new MyClass);

    for (std::list<MyClass*>::const_iterator it = MyClass::GetAllObjects_Begin(), end_it = MyClass::GetAllObjects_End(); it != end_it; ++it)
    {
        const MyClass& obj = **it;
        std::cout << obj.ToString() << "\n";
    }

    while (! objects.empty())
    {
        delete objects.back();
        objects.pop_back();
    }
}
Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
  • Yes, an alternate solution would have been fine, but luckily I managed to implement one myself. Thanks for the response. – Publius Feb 19 '13 at 18:17
1

This line is trouble:

tests.erase(i);

An iterator pointing to the erased element is invalid, and you can't increment it any longer. Luckily, erase returns a new iterator you can use:

auto i = tests.begin();
while (i != tests.end())
{
    if (i->use_count() == 0 || i->expired())
    {
        i = tests.erase(i);
    }
    else {
        std::cout << i->lock()->val << std::endl;
        ++i;
    }
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720