Search code examples
c++oopada

Ada interfacing C++: instance destroyed


I want to use a C++ library that implements the Factory Method design pattern.

Below you can see a minimal reproducibable example, including C++ sources and the Ada adapter.

  • Item.h:

    // Product interface
    
    #ifndef _ITEM_H_
    #define _ITEM_H_
    
    class Item {
    public:
    
      virtual ~Item() = default;
      virtual void do_something() = 0;
    };
    
    #endif
    
  • ConcreteItem.h:

    
    #ifndef _CONCRETEITEM_H_
    #define _CONCRETEITEM_H_
    
    #include "Item.h"
    
    class ConcreteItem : public Item {
    public:
      ConcreteItem();
      ~ConcreteItem();
      void do_something();
    };
    
    #endif
    
  • ConcreteItem.cpp:

    
    #include <stdlib.h>
    #include <iostream>
    #include "ConcreteItem.h"
    
    void ConcreteItem::do_something() {
      std::cout << "Doing stuff \n";
    };
    
    ConcreteItem::ConcreteItem() {
      std::cout << "Concrete Item created \n";
    };
    
    ConcreteItem::~ConcreteItem(){
      std::cout << "Concrete Item destroyed \n";
    };
    
  • Factory.h:

    
    #ifndef _FACTORY_H_
    #define _FACTORY_H_
    
    #include "Item.h"
    
    class Factory {
    public:
      static Item* get_configured_item ();
      Factory(Factory& other) = delete;
    
    private:
    
      static Factory* factory;
      static Item* config_item;
    
      Factory();
      ~Factory();
    };
    
    #endif
    
  • Factory.cpp:

    
    #include "Factory.h"
    #include "ConcreteItem.h"
    
    Factory* Factory::factory = nullptr;
    Item* Factory::config_item = nullptr;
    
    Item *Factory::get_configured_item() {
    
      if (Factory::factory == nullptr) {
        Factory::factory = new Factory();
    
        if (Factory::config_item == nullptr) {
          Factory::config_item = new ConcreteItem();
        };
      };
    
      return Factory::config_item;
    };
    
    Factory::Factory(){};
    Factory::~Factory() {
      delete Factory::config_item;
    };
    

And here I have the Ada files that imports C++ symbols, generated with a call to g++ -c -fdump-ada-spec -C ./Factory.h and doing some modifications to suite my taste:

  • Factory_h.ads:
    limited with Item_h;
    
    package Factory_h is
    
      type Factory is limited record
        null;
      end record
        with Import => True,
        Convention => CPP;
    
      function get_configured_item return access Item_h.Item'Class  -- ./Factory.h:9
        with Import => True, 
        Convention => CPP, 
        External_Name => "_ZN7Factory19get_configured_itemEv";
    
    end Factory_h;
    
  • Item_h.ads:
    
    with Interfaces.C;
    
    package Item_h is
      type Item is limited interface
        with Import => True,
        Convention => CPP;
    
      --  procedure Delete_Item (this : access Item) is abstract;-- ./Item.h:8
      --  
      --  procedure Delete_And_Free_Item (this : access Item) is abstract; -- ./Item.h:8;
    
      procedure do_something (this : access Item) is abstract;  -- ./Item.h:9
    end Item_h;
    

And finaly an Ada main to test this silly example:

with Factory_h;
with Item_h;

procedure main is
  
  configured_item : constant access Item_h.Item'Class :=
    Factory_h.get_configured_item;
  
begin
  
  configured_item.do_something;

end main;

Do you know why if I comment out the Item primitives Delete_Item and Delete_And_Free_Item the call to do_something is never done and the item is destroyed?

If I uncomment them everything works.

Thanks in advance!


Solution

  • I'm far from expert on Ada-C++ interfacing, but my guess is that in the C++ code, do_something is the third entry in the virtual-function table for ConcreteItem. The dump-ada-spec operation adds Delete_Item and Delete_And_Free_Item to occupy the first two slots in the v-table on the Ada side, which makes do_something the third entry in the v-table, matching its position on the C++ side.

    When you comment out Delete_Item and Delete_And_Free_Item this makes do_something be the first entry in the v-table on the Ada side. So when Ada calls do_something, it actually calls the first v-table entry, which on the C++ side is some other function -- apparently one that performs Delete_Item.