0

I'm attempting to implement an extension of an abstract class, as a concrete class; but can't figure out why it's continually compiling to abstract. I think I'm missing a pure virtual somewhere, but can't find it.

I'm building an extension onto the Godot Game Engine, and while my code is compiling, it's telling me time and again that the class I built is abstract. I don't intend for it to be abstract, and I believe I've implemented all of the pure virtuals; but I'm clearly wrong about that or I would have something concrete, wouldn't I?

The compilation chain is complex enough to limit me to picking through source code and trying to find = 0; code that I haven't touched; I don't seem to have reasonable access to simply adding a main function and attempting to instance the class, because I'm building an so and a dll. I tried adding a main to squeeze out a detailed error message, and just building with gcc, but the -I for it is apparently all over the place, and it's usually managed by SCons.

I can't help but think that there must be an easier way to find out than this. The Godot editor hasn't been especially helpful, and while there's a very popular action item for it, it isn't yet possible to extend an abstract class with the internal GDScript and backtrace it to C++ from there.

I'm using Linux Mint and have access to objdump and nm; nm hasn't been terribly useful but I'm open to suggestions with either. I believe that SCons is using gcc, not clang, but am not entirely sure.

Here's my header:

#ifndef NULLARY_MULTIPLAYER_PEER
#define NULLARY_MULTIPLAYER_PEER

#include <godot_cpp/classes/multiplayer_peer.hpp>
#include <godot_cpp/classes/crypto.hpp>

namespace godot {

class NullaryMultiplayerPeer : public MultiplayerPeer {
    GDCLASS(NullaryMultiplayerPeer, MultiplayerPeer);
    
private:

protected:
    static void _bind_methods();

public:
    NullaryMultiplayerPeer();
    ~NullaryMultiplayerPeer();

    void set_transfer_channel(int p_channel);
    int get_transfer_channel() const;
    void set_transfer_mode(TransferMode p_mode);
    TransferMode get_transfer_mode() const;
    void set_refuse_new_connections(bool p_enable);
    bool is_refusing_new_connections() const;
    bool is_server_relay_supported() const;

    void set_target_peer(int p_peer_id);

    int get_packet_peer() const;
    TransferMode get_packet_mode() const;
    int get_packet_channel() const;

    void disconnect_peer(int p_peer, bool p_force = false);

    bool is_server() const;

    void poll();
    void close();

    int get_unique_id() const;

    ConnectionStatus get_connection_status() const;
};

}

#endif

And here's my CPP:

#include "nullary_multiplayer_peer.h"
#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void NullaryMultiplayerPeer::_bind_methods() {
}

NullaryMultiplayerPeer::NullaryMultiplayerPeer() {
}

NullaryMultiplayerPeer::~NullaryMultiplayerPeer() {
}

void NullaryMultiplayerPeer::set_transfer_channel(int p_channel) {}

int NullaryMultiplayerPeer::get_transfer_channel() const { return 0; }

void NullaryMultiplayerPeer::set_transfer_mode(TransferMode p_mode) {}

MultiplayerPeer::TransferMode NullaryMultiplayerPeer::get_transfer_mode() const { return MultiplayerPeer::TransferMode::TRANSFER_MODE_UNRELIABLE; }

void NullaryMultiplayerPeer::set_refuse_new_connections(bool p_enable) {}

bool NullaryMultiplayerPeer::is_refusing_new_connections() const { return true; }

bool NullaryMultiplayerPeer::is_server_relay_supported() const { return false; }

void NullaryMultiplayerPeer::set_target_peer(int p_peer_id) { }

int NullaryMultiplayerPeer::get_packet_peer() const { return 0; }
MultiplayerPeer::TransferMode NullaryMultiplayerPeer::get_packet_mode() const { return MultiplayerPeer::TransferMode::TRANSFER_MODE_UNRELIABLE; }
int NullaryMultiplayerPeer::get_packet_channel() const { return 0; }

void NullaryMultiplayerPeer::disconnect_peer(int p_peer, bool p_force) { }

bool NullaryMultiplayerPeer::is_server() const { return false; }

void NullaryMultiplayerPeer::poll() {}
void NullaryMultiplayerPeer::close() {}

int NullaryMultiplayerPeer::get_unique_id() const { return 0; }

MultiplayerPeer::ConnectionStatus NullaryMultiplayerPeer::get_connection_status() const { return MultiplayerPeer::ConnectionStatus::CONNECTION_DISCONNECTED; }

It's still building to an uninstantiable abstract class. I'm not sure what I'm missing.

Thanks.

Michael Macha
  • 1,729
  • 1
  • 16
  • 25
  • 2
    you do not need to have a `main`. Creating an instance anywhere should trigger the compiler to produce error message for non implemented methods – 463035818_is_not_an_ai Jun 01 '23 at 17:55
  • 1
    If any of the functions in the baseclass are virtual make sure you mark them override in the derived class. – Pepijn Kramer Jun 01 '23 at 17:57
  • 6
    What is the error message? They normally call out the function that makes it abstract. – NathanOliver Jun 01 '23 at 17:57
  • 2
    Unrelated pedantic note: You don't need to, and probably don't want to, put your stuff into the `godot` namespace. – user4581301 Jun 01 '23 at 17:57
  • 1
    Another unrelated pedantic note: If a destructor does nothing, consider leaving it out (the compiler will make one for you. See the [Rule of Zero](https://en.cppreference.com/w/cpp/language/rule_of_three)) or explicitly defaulting it in the declaration (`~NullaryMultiplayerPeer();` becomes `~NullaryMultiplayerPeer() = default;`). – user4581301 Jun 01 '23 at 18:01
  • 3
    you declare the methods in the namespace `godot` but then define them in the global namespace. `using namespace godot;` does *not* place the following definitions inside that namespace – 463035818_is_not_an_ai Jun 01 '23 at 18:01
  • the class you define is `godot::NullaryMultiplayerPeer` then you define methods of a different class `NullaryMultiplayerPeer` – 463035818_is_not_an_ai Jun 01 '23 at 18:02
  • 2
    Huh. My unrelated pedantic note was not so pedantic. – user4581301 Jun 01 '23 at 18:02
  • 1
    The pedantry of methods being implemented in the wrong namespace wouldn't cause a class to be abstract though. It would merely cause linker errors. – Mooing Duck Jun 01 '23 at 18:06
  • The `using namespace godot;` looks like it might be cleaning up after the missing namespace in the definitions: https://godbolt.org/z/cGcnhfbs8 – user4581301 Jun 01 '23 at 18:07
  • 2
    As pepijn mentioned it's definetly a good idea to add the `override` modifier to functions that are supposed to implement/"replace" function of the base class. This can help you by having the compiler point out functions that you thought you overwrote, but where you got the signature wrong, e.g. `struct A{ virtual void f(int&) = 0; virtual void f2() const = 0; }; struct B : A { void f(int) override; void f2() override; };` results in 2 compiler errors pointing out issues that could easily be missed otherwise... https://godbolt.org/z/WfWTPWzoG – fabian Jun 01 '23 at 18:11
  • To clarify, this is a silent bug; it implements fine, it just implements wrong. I want an instantiable class, I'm getting an abstract one. I'm not sure why it's still abstract, though! I just set it up to include the class in the godot namespace—which, for my long game, I definitely do want it in, as this is standard for GDExtension and kind of expected. I'm going to attempt to add override now, thank you for all your comments! – Michael Macha Jun 01 '23 at 18:18
  • Btw: Something like `static_assert(!std::is_abstract_v);` may help end the build process early, if you haven't implemented all the necessary functions... – fabian Jun 01 '23 at 18:19
  • Interesting! I just noticed that there's a significant difference between the language binding, in godot-cpp, and the core source code; multiplayer_peer.h has a number of explicit virtuals; but multiplayer_peer.hpp (in the language binding) doesn't appear to have any. And sure enough, when I add override, it gives me an error message on compilation. There's also a suspicious looking `register_virtuals` function in the binding header. This is definite progress... is there any way I could be creating an abstract class without virtuals in this language? – Michael Macha Jun 01 '23 at 18:27
  • _"is there any way I could be creating an abstract class without virtuals in this language?"_ - Abstract in what sense? You can make a base class non-constructible on its own if that's what you want. – Ted Lyngmo Jun 01 '23 at 19:51
  • @TedLyngmo No, that's not what I want here; I'm trying to figure out why an extension of an abstract class is still abstract. I have found something... it seems that a lot of these funny code shenanigans deal with building Godot itself, from source; which is something I've been meaning to look at anyway and is quite well documented. Looks like I know what I'm researching tomorrow! I'll update this question when I figure it all out. I appreciate the support. – Michael Macha Jun 02 '23 at 04:08
  • _"I'm trying to figure out why an extension of an abstract class is still abstract"_ - Maybe I missed something in the earlier comments but the reason is because not all of the pure `virtual` methods have implementations. Your compiler should tell you exactly which. – Ted Lyngmo Jun 02 '23 at 09:13
  • @TedLyngmo That's precisely the problem—it is compiling to a library, which includes this class. This class is, successfully, compiling; but is abstract; as you said, I'm missing a virtual. Since it is accessed at its point of use with another language, which does not provide an adequate back trace, there is no compiler message—it is a silent bug. There is no trace to work with. If there is a manner in which I can use something like SCons or even gcc to determine what these functions are, in a working library, that would be a satisfactory and usable solution. – Michael Macha Jun 04 '23 at 05:42
  • Make a C++ program that tries to instantiate the same class and then you should get a clear error message. – Ted Lyngmo Jun 04 '23 at 05:57

0 Answers0