Search code examples
c++oopfunction-pointers

Is it possible to use a pointer to an uninstantiated object as a variable in C++?


I want an attribute in a C++ class be an uninstantiated class from a particular class heirachy. All members of this class heirachy would then implement the same method, meaning I could instantiate the object and then use the method when the situation calls for it. Here's some code (that doesn't compile) demonstrating what I mean:


#include <iostream>

using namespace std;

class Event {
public:
    Event() = default;
    virtual void go() = 0;
};

class EventA : Event {
public:
    EventA() = default;

    void go(){
        cout << "Running event A"<< endl;
    }
};

class EventB : Event {
public:
    EventB() = default;


    void go(){
        cout << "Running event B"<< endl;
    }
};

class Situation{
private:
    Event* current_event = &EventA;   //Problematic code: EventA does not refer to a value
public:
    Situation() = default;

    void setEvent(Event* event){
        current_event = event;
    }

    void runEvent(){
        current_event.go();
    }
};

int main() {

    Situation situation;
    situation.runEvent();
    situation.setEvent(&EventB);
    situation.runEvent();

    return 0;
};


Solution

  • In two places, you seem to be doing what could be described as trying to take a pointer from a type:

    Event* current_event = &EventA;
    

    and

    situation.setEvent(&EventB);
    

    This doesn't work and is not really a thing with proper meaning in C++. What you are trying to do could be implemented in 3 different ways I can think of.

    Method 1: instead of having a class, you can have a function pointer, and pass the function pointer as parameter:

    #include <iostream>
    
    using namespace std;
    
    void eventA_go(){
        cout << "Running event A"<< endl;
    }
    
    void eventB_go(){
        cout << "Running event B"<< endl;
    }
    
    class Situation{
    private:
        using EventFunctionPtr = void (*)();
        EventFunctionPtr current_event = &eventA_go;
    public:
        Situation() = default;
    
        void setEvent(EventFunctionPtr event){
            current_event = event;
        }
    
        void runEvent(){
            current_event();
        }
    };
    
    int main() {
    
        Situation situation;
        situation.runEvent();
        situation.setEvent(&eventB_go);
        situation.runEvent();
    
        return 0;
    };
    

    Method 2: you can make this code a little more generic, by allowing any type of callable in your Situation class, not only function pointers:

    #include <iostream>
    #include <functional>
    
    using namespace std;
    
    void eventA_go(){
        cout << "Running event A"<< endl;
    }
    
    void eventB_go(){
        cout << "Running event B"<< endl;
    }
    
    class Situation{
    private:
        std::function<void ()> current_event = eventA_go;
    public:
        Situation() = default;
    
        template <typename F>
        void setEvent(F&& event){
            current_event = event;
        }
    
        void runEvent(){
            current_event();
        }
    };
    
    int main() {
    
        Situation situation;
        situation.runEvent();
        situation.setEvent(&eventB_go);
        situation.runEvent();
    
        return 0;
    };
    

    Method 3: you can go back to your original idea of having a base class that must be implemented to provide a go() method, but in this case you will actually have to make sure the objects you are calling do exists. A possible way to do it is with std::unique_ptr:

    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class Event {
    public:
        Event() = default;
        virtual ~Event() = default;
        virtual void go() = 0;
    };
    
    class EventA : public Event {
    public:
        EventA() = default;
    
        void go(){
            cout << "Running event A"<< endl;
        }
    };
    
    class EventB : public Event {
    public:
        EventB() = default;
    
        void go(){
            cout << "Running event B"<< endl;
        }
    };
    
    class Situation{
    private:
        std::unique_ptr<Event> current_event = std::make_unique<EventA>();
    public:
        Situation() = default;
    
        void setEvent(std::unique_ptr<Event>&& event){
            current_event = std::move(event);
        }
    
        void runEvent(){
            current_event->go();
        }
    };
    
    int main() {
    
        Situation situation;
        situation.runEvent();
        situation.setEvent(std::make_unique<EventB>());
        situation.runEvent();
    
        return 0;
    };
    

    Notice that, in this case, the destructor of the abstract class must be virtual, and the inheritance must be public.