0

I have a simple container class that points to an abstract class and I have functions to get/set the pointer in the container class. More concretely, the class looks like this:

class Container
{
    Abstract* thing;
public:
    void set(Abstract &obj)
    {
        thing = &obj; //danger of dangling pointer
    }

    Abstract* get()
    {
        return thing;
    }
};

Abstract is an abstract class. As can be seen already, there's a danger of a dangling pointer. I know that I could make a copy of the object (new) and then point to it. But I can't create an instance of an abstract class. What solutions to this are there?


The following are just more information:

Class definitions

class Abstract
{
public:
    virtual void something() = 0;
};

class Base : public Abstract
{
    int a;
public:
    Base() {}
    Base(int a) : a(a){}
    virtual void something()
    {
        cout << "Base" << endl;
    }
};

class Derived : public Base
{
    int b;
public:
    Derived() {}
    Derived(int a, int b) : Base(a), b(b){}
    virtual void something()
    {
        cout << "Derived" << endl;
    }
};

Simple tests

void setBase(Container &toSet)
{
    Base base(15);
    toSet.set(base);
}

void setDerived(Container &toSet)
{
    Derived derived(10, 30);
    toSet.set(derived);
}

int main()
{
    Container co;

    Base base(15);
    Derived derived(10, 30);

    Base *basePtr;
    Derived *derivedPtr;

    //This is fine
    co.set(base);
    basePtr = static_cast<Base *>(co.get());
    basePtr->something();

    //This is fine
    co.set(derived);
    derivedPtr = static_cast<Derived *>(co.get());
    derivedPtr->something();

    //Reset
    basePtr = nullptr;
    derivedPtr = nullptr;

    //Dangling pointer!
    setBase(co);
    basePtr = static_cast<Base *>(co.get());
    basePtr->something();

    //Dangling pointer!
    setDerived(co);
    derivedPtr = static_cast<Derived *>(co.get());
    derivedPtr->something();

    return 0;
}
silentwf
  • 153
  • 2
  • 8

3 Answers3

6

What you need to do is to define your memory ownership concretely.

Container::set accepts an instance of Abstract by reference, which usually does not imply an ownership transfer:

 void set(Abstract &obj){...} // Caller retains ownership of obj, but now we have a weak reference to it

Then the onus of deletion is not on you.

Container::get returns a pointer which implies ownership, indicating that someone who calls set should not invalidate the passed object.

Abstract* get(){...}

This could be problematic, as you've stated.

You have a few options

  • Encode these memory ownership semantics within Container with proper documentation (Code by contract)
  • Use a smart pointer like std::shared_ptr

In the former case, whether it works or not depends on the user reading and understanding your API, and then behaving nicely with it. In the latter case, the pointer object owns itself, and will delete the allocated memory when the last instance goes out of scope.

 void set(std::shared_ptr<Abstract> obj){...} 
// now Container participates in the lifetime of obj, 
// and it's harder to nullify the underlying object 
// (you'd have to be intentionally misbehaving)
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Because I hate stupidity like `delete myContainer.get();` can I talk you into `Abstract& get(){...}`? – user4581301 Jul 25 '16 at 23:36
  • @user4581301: Why would you be deleting `myContainer.get()`? It will delete itself, and calling `delete` manually will result in a segmentation fault. – AndyG Jul 26 '16 at 11:48
  • Yup. It's careless, but you can do it. The problem will likely not be with blatant and immediate error, but mis-handling what looks like a raw pointer a few dozen lines of code later. The reference ramps up the difficulty of accidentally screwing up into the intentionally misbehaving category. – user4581301 Jul 26 '16 at 16:11
  • @AndyG Curious how the first half solves the problem though? Assuming that there's no "get" function, then I'd still have to use smart pointers no? – silentwf Jul 26 '16 at 19:36
  • You don't need to use smart pointers if the user is well-behaved. Design by contract assume preconditions and postconditions are met. Therefore, a precondition on `get` is that the data added in the previous `set` has not been invalidated. – AndyG Jul 26 '16 at 20:46
1

If you are worried about the object being deallocated elsewhere resulting in a dangling pointer, you could use boost smart pointers.

Boost smart pointers would provide you the service of book keeping and help to avoid such a case.

Some information can be found here : smart pointers (boost) explained

Community
  • 1
  • 1
krembo
  • 25
  • 6
1

This is what std::unique_ptr is for:

class Container
{
    std::unique_ptr<Abstract> thing;
public:
    void set(std::unique_ptr<Abstract> obj)
    {
        thing = obj;
    }

    Abstract* get()
    {
        return thing.get();
    }
};

Now the Abstract object is "owned" by Container and will be cleaned up automatically when the Conatiner is destroyed.

If you want a pointer that might live longer, or might be shared between mulitple containers, use std::shared_ptr instead.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226