简体   繁体   中英

Why can't I return a reference to a unique_ptr of a derived class created with a template?

I have a compile time "plugin" system based on this link , which I want to retain ownership of the plugins, which are literally unique - there should only be one instance of each type.

There is a registrar class which owns a plugin type, and owns a unique_ptr to the plugin.

I want to return a reference to the plugin whenever one is requested, to keep ownership clear.

More complete code below. Note the #define for registration in the header of a plugin.

Also, I'm renaming from Renderer in my code to Plugin here so if I've missed any that's what the random Render references are/were.

Factory class:

class PluginFactory
    {
    public:
        // Instance of Factory
        static PluginFactory& Instance();
        void Register(IPluginRegistrar *Registrar, std::string name);
        
        IPlugin& GetPlugin(std::string name);

    private:
        std::unordered_map<std::string, IPluginRegistrar *>_registry;
        // private constructor, prevent copying
        PluginFactory() : _registry() {};
        PluginFactory(PluginFactoryconst&) = delete;
        void operator=(PluginFactoryconst&) = delete;
    };
    
    PluginFactory& PluginFactory::Instance()
    {
        static PluginFactoryinstance;
        return instance;
    }
    void PluginFactory::Register(IPluginRegistrar *registrar, std::string name)
    {
        if (_registry.count(name))
            std::cout << name << " is already registered!\n";
        else
        {
            std::cout << "Registering " << name << "\n";
            _registry[name] = registrar;
        }
    }
    IPlugin& PluginFactory::GetPlugin(std::string name)
    {
        IPluginRegistrar* registrar = _registry.at(name);        
        return registrar->GetPlugin();
    }
    #define REGISTER_PLUGIN(CLASSNAME) \
    namespace { \
    static MyNamespace::PluginRegistrar<CLASSNAME>\
    _registrar( #CLASSNAME ); \
    };

Plugin Base

    class IPlugin
    {
    public:
        IPlugin() { std::cout << "Constructing IPlugin class\n";  }
        virtual void DoThing(){ std::cout << "Here's the thing from an IPlugin!\n"; };
        
    };

Plugin Derived

    class TestPlugin : public IPlugin
    {
    public:
        TestPlugin ();        
        virtual void DoThing() override;
    };
    REGISTER_RENDERER(TestPlugin)

    TestPlugin::TestPlugin () : IPlugin()
    {
        std::cout << "Constructing TestPlugin class\n";
    }

    void TestPlugin::DoThing()
    {
        std::cout << "Here's the thing from a TestPlugin!\n";
    }

Registrar

 class IPluginRegistrar
    {
    public:
        virtual IPlugin& GetPlugin() = 0;
  
    };

 template <class T>
    class PluginRegistrar: public IPluginRegistrar
    {
    public:
        PluginRegistrar(std::string classname); // constructor registers plugin with a plugin factory
        IPlugin& GetPlugin(); // GetPlugin is called by the factory
    private:
        std::string _name;    
        std::unique_ptr<IPlugin> _ptr;
    };

    template <class T>
    PluginRegistrar<T>::PluginRegistrar(std::string classname) : _name(classname)
    {
        PluginFactory& factory = PluginFactory::Instance();        
        factory.Register(this, classname);
    }

template <class T>
    IPlugin &
    PluginRegistrar<T>::GetPlugin()
    {
        if (!_ptr)
            _ptr = std::make_unique<T>();
        return *_ptr;                    
    }
    

Code in use:

    auto& factory = PluginFactory::Instance();
    auto plugin= factory.GetPlugin("TestPlugin");
    => outputs confirm derived class constructed
    plugin.DoThing();
    => output of IPlugin.DoThing(), not TestPlugin.DoThing()

The original version of this code returned a new unique_ptr<IPlugin> each time GetPlugin() was called, and worked great. However now I've switched return type to an IPlugin & , when I call a virtual function on the result, the base class function is called, not the derived.

auto plugin= factory.GetPlugin("TestPlugin");

This will copy the result into a new object, which will be a IPlugin (the original derived type will be sliced ). You want to use a reference instead:

auto& plugin= factory.GetPlugin("TestPlugin");

You might want to make IPlugin::DoThing() a pure virtual function (ie virtual void DoThing() = 0 ) which will

  1. Force derived classes to implement it
  2. Prevent you from having an object of type IPlugin , ie make your original code fail to compile

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM