Advertisement

Multiple inheritance and destructor

Started by October 28, 2024 07:20 AM
8 comments, last by 1vanK 4 weeks ago

C++:

class RefCounted
{
private:
    i32 ref_count_;

public:
    RefCounted()
    {
        ref_count_ = 1;
    }

    ~RefCounted()
    {
        std::cout << "~RefCounted()\n";
    }

    void add_ref()
    {
        ++ref_count_;
    }

    void release()
    {
        if (--ref_count_ == 0)
            delete this;
    }
};

Image::~Image()
{
    ...
    std::cout << "~Image()\n";
}

class ASImage : public RefCounted, public Image
{
    ~ASImage()
    {
        cout << "~ASImage()\n";
    }
};

AS:

void main()
{
   Image@ img = Image();
}

Output:

~RefCounted()

Adding virtual to ~RefCounted() fixed this.

Please add somewhere a information that class destructors when multiple inheritance must be virtual.

This is not an issue with AS. Your code without multiple inheritance has memory-leaks, even when using in raw C++:

void release()
{
        if (--ref_count_ == 0)
            delete this;
}

This deletes an instance of Type “RefCounted”. Since you declared the ctor as non-virtual, it will execute this destructor only:

~RefCounted()
{
        std::cout << "~RefCounted()\n";
}

Which is what you are seeing. It needs the virtual on the destructor to be able to delegate this to the deriving classes, whether single or multiple inheritance.

class A {};
class B : A { std::string string };

A* pA = new B();
delete pA; // memory leak, will only call "~A", same as your case

As a rule of thumb, any class that inherits another class, which may be stored and destroyed via a pointer of the base-class, needs to have a virtual destructor. Otherwise, you should make the destructor “protected” to prevent accidential memory-leaks. But this would not have helped in this case.

PS: I don't know if this sample is based on some AS-docu, and might benefit from adding this information about how interfacing with C++ works, but as stated it is basically just something you didn't know about the C++-language.

Advertisement

Juliean is correct. This is a C++ best practices issue.

I'll see if there is an appropriate place in the AngelScript documentation to add a reminder to the application developers. But most likely it something that won't even be seen by most, since it is not something that would influence how the code is registered with AngelScript.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Thanks for the clarification

WitchLord said:
since it is not something that would influence how the code is registered with AngelScript.

Classes in games often have a base class with a counter

C++ code:


class RefCounted
{
private:
    int ref_count_;
public:
    RefCounted()
    {
        ref_count_ = 1;
    }
    virtual ~RefCounted()
    {
    }
    void add_ref()
    {
        ++ref_count_;
    }
    void release()
    {
        if (--ref_count_ == 0)
            delete this;
    }
};
class Image
{
private:
    int x_;
    int y_;
public:
    Image(int x, int y)
        : x_(x)
        , y_(y)
    {
        std::cout << "Image::Image(" << x_ << ", " << y_ << ");" << std::endl;
    }
    void save(const string& path)
    {
        std::cout << "Image::save(): x=" << x_ << ", y=" << y_ << std::endl;
    }
};
class ASImage : public RefCounted, public Image
{
public:
    using Image::Image;
};
static ASImage* image_factory(int x, int y)
{
    return new ASImage(x, y);
}
void register_image(asIScriptEngine* e)
{
    e->RegisterObjectType("Image", 0, asOBJ_REF);
    e->RegisterObjectBehaviour("Image", asBEHAVE_FACTORY, "Image @f(int, int)", asFUNCTION(image_factory), asCALL_CDECL);
    e->RegisterObjectBehaviour("Image", asBEHAVE_ADDREF, "void f()", asMETHODPR(ASImage, add_ref, (), void), asCALL_THISCALL);
    e->RegisterObjectBehaviour("Image", asBEHAVE_RELEASE, "void f()", asMETHODPR(ASImage, release, (), void), asCALL_THISCALL);
    e->RegisterObjectMethod("Image", "void save(const string &in)", asMETHODPR(Image, save, (const string&), void), asCALL_THISCALL);
}

AS Code:

void main()
{
   Image@ img = Image(10, 20);
   img.save("aaa");
}

Output:

Image::Image(10, 20);
Image::save(): x=18495400, y=1

Any ideas?

Advertisement

This works without problems (without multiple inhariance):

class Image : public RefCounted
{
    …
};
class ASImage : public Image
{
public:
    using Image::Image;
};

1vanK said:
e->RegisterObjectMethod("Image", "void save(const string &in)", asMETHODPR(Image, save, (const string&), void), asCALL_THISCALL);

You need to use asMETHODPR(ASImage, save, (const string&), void)

Otherwise the method pointer doesn't include the correct base offset to AngelScript to invoke the C++ method.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Oh, thanks, I didn't notice that.

Advertisement