Jak zrobić żeby klasa dziedzicząca mogła przeładować funkcje z parametrem B, który dziedziczy od A, funkcje interfejsu z parametrem A.

0

Mam klase HandleBase, która jest taką ogólną klasą trzymającą ID

struct HandleBase
{
	using HandleID = uint32_t;
	static constexpr HandleID INVALID_HANDLE_ID = HandleID{ std::numeric_limits<HandleID>::max() };

	HandleBase() = default;

	HandleBase(HandleID ID)
		: object(ID) {}

	inline void clear() { object = INVALID_HANDLE_ID; }

	inline auto operator<=>(const HandleBase& other) const { return object <=> other.object; }

	[[nodiscard]] inline HandleID getID() const { return object; }

protected:
	HandleID object = INVALID_HANDLE_ID;
};

i klasę, Handle, która jest template i jako T podaje się jakąś klasę, na którą wskazuje ten ID

template <typename T>
struct Handle : HandleBase
{
	using HandleBase::HandleBase;

	template<typename U>
	Handle(const Handle<U>& base)
		: HandleBase(base) 
	{
	}
};

mam też klasę, która posiada właśnie ten ID

struct Object
{
  Handle<Object> ID;
};

i konkretną implementację jakiegoś obiektu

struct ObjectA : Object
{};

oraz klasę, która jest interfejsem do zarządzania obiektami. Ma ona metodę wirtualną, która przyjmuje jako parameter właśnie Handle

struct ObjectManager
{
  virtual Object* getObjectByID(Handle<Object> handle) = 0;
};

i konkretną implementacje menażdżera obiektu A

struct ObjectAManager : ObjectManager
{
  ObjectA* getObjectByID(Handle<ObjectA> handle) override {//... coś tam}
};

no i wszystko fajnie, tylko, że podczas kompilacji pokazuje się błąd, że ObjectAManager did not override any base class methods.

gdy zamiast Handle<Object> w ObjectManager i Handle<ObjectA> w ObjectAManager dam po prostu uint32_t działa ideolo, bo wiadomo, int to int, ale chciałbym posługiwać się klasą Handle

W jaki sposób to zrobić?

próbowałem dodać

auto operator uint32_t() const { return object }

do klasy HandleBase żeby może kompilator spróbował potraktować te klasy jako uint32_t ale nie zadziałało.

Jest jakiś sposób na to żeby nie było erroru did not override any base class methods?

edit

użycie

std::vector<ObjectA> objects;
for(uint32_t i = 0; i < 10; ++i) {
  auto& object = objects.emplace_back();
  object.id = Handle<ObjectA>(i);
}

ObjectAManager manager;
auto fifthObjectHandle = Handle<ObjectA>{5};
ObjectA fifthObject = manager.getObjectByID(fifthObjectHandle);

ObjectManager nigdy nie będzie używany czy tworzony przez "end usera". Jest używany tylko do innych internalowych klas.

Gdy np. pojawi się ObjectB, to musi on mieć swój manager ObjectBManager i "end user" używa tego konkretnego managera

2

Overridować możesz tylko funkcje wirtualne. Ale i wtedy musi się zgadzać sygnatura, a więc typy muszą być te same.

Może pokaż jak chcesz tego używać, bo na początku myślałem, że odpowiedzią będzie CRTP, ale zapewne jednak nie.

1

Handle<Object> oraz Handle<ObjectA> to są dwa różne typy zatem nie możesz ich używać w ten sposób. Za to możesz spróbować z klasą bazową:

struct ObjectManager
{
  virtual Object* getObjectByID(const HandleBase& handle) = 0;
};
1

Nie możesz zmienić typu parametru przy overrideowaniu funkcji.

Powód jest prosty: funkcję możesz wywołać mając interfejs.

struct ObjectManager
{
  virtual Object* getObjectByID(Handle<Object> handle) = 0;
};

struct ObjectAManager : ObjectManager
{
  ObjectA* getObjectByID(Handle<ObjectA> handle) override {}
};

ObjectAManager *mgr1 = ...;

Handle<Object> objectB = ...;

ObjectManager *mgr = mgr1;
mgr->getObjectByID( objectB ); // ObjectAManager::getObjectByID zamiast typu Handle<ObjectA> dostaje typ Handle<Object>. Coś jest nie tak.

Sądząc po nazwie funkcji interesuje cię raczej inny typ parametru:

struct ObjectManager
{
  virtual Object* getObjectByID(HandleBase::HandleID id) = 0;
};

struct ObjectAManager : ObjectManager
{
  virtual ObjectA* getObjectByID(HandleBase::HandleID id) override { /* implementacja */ }
};

ObjectAManager *objectsAMgr = ...;

objectsAMgr->getObjectByID( ... ); // zwraca ObjectA*, bo wiemy, że objectsAMgr jest typu ObjectAManager.

ObjectManager *objectsUnknownMgr = objectsAMgr;
objectsUnknownMgr->getObjectByID( ... ); // zwraca Object*, nie wiemy jakim typem obiektu zajmuje się dany manager.

Zwróć uwagę, że możesz zwrócić inny typ w overrideowanej funkcji, ale musi to być tzw. covariant.

https://en.cppreference.com/w/cpp/language/virtual (patrz: Covariant return types)


Ewentualnie jest jeszcze jedna opcja.

Jeśli dysponujesz obiektem typu Handle<ObjectA> dzięki czemu wiesz, jaki jest ID obiektu i na tej podstawie chcesz dostać ObjectA * to potrzebujesz nieco innego rozwiązania.

using HandleID = uint32_t;

class HandleBase
{
public:
    HandleBase( HandleID handleID ) : id( handleID ) {}    

	HandleID getID() const { return id; }

private:
    HandleID id = 0;
};

template <typename T>
class Handle : public HandleBase
{
public:
	using HandleBase::HandleBase;
};

class Object
{
public:
    Object( HandleID handleID ) : handle( handleID ) {}

    const Handle< Object > &getHandle() const { return handle; }

private:
    Handle< Object > handle;
};

class ObjectManager
{
public:
  template< typename T >
  T *getObjectByID( Handle< T > handle )
  {
    static_assert( std::is_base_of< Object, T >::value == true, "T is not of type Object" );
    return static_cast< T * >( getObjectByIDImpl( handle.getID() ) );
  }

protected:
  virtual Object* getObjectByIDImpl( HandleID id ) = 0;
};

///////////////////////////////////////////////////////////

class ObjectA : public Object
{
public:
	using Object::Object;

    std::string someString;
};

class ObjectAManager : public ObjectManager
{
public:
    ObjectAManager()
    {
        auto object = std::make_unique< ObjectA >( 10 );
        object->someString = "aha";
        objects.push_back( std::move( object ) );
    }

protected:
    virtual ObjectA* getObjectByIDImpl( HandleID id ) override
    {
        for( const auto &e : objects )
        {
            if( e->getHandle().getID() == id )
            {
                return e.get();
            }
        }
        return nullptr;
    }

private:
    std::vector< std::unique_ptr< ObjectA > > objects;
};


int main()
{
    ObjectAManager mgr;

    Handle< ObjectA > object10Handle( 10 );

    ObjectManager *mgrInterface = &mgr;

    ObjectA *o = mgrInterface->getObjectByID( object10Handle );
    std::cout << o->someString << std::endl;
}

https://godbolt.org/z/csW6YEhrr

Pytanie pozostaje tylko, czy na pewno potrzeba ci takiego interfejsu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1