Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterator interface #1

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

Conversation

vaczijani
Copy link

Added the iterator interface to RegistrarBase class and changed the vector to static to avoid needed to handle nullptr.
This is my very first contribution to any github project, so let me know if I'm doing this incorrectly.
Best regards,
Janos

@wolframroesler
Copy link

Looks very good, thanks! Will review and test it, and maybe rework it to match naming conventions etc.

@vaczijani
Copy link
Author

Shame on me! Recently read your Coding Standards and agreed with it. Will check back to make better work next time. Didn't want to create extra work for you. Many thanks.

README.md Outdated
linktimeplugin::RegistrarBase<PluginBase>::begin(),
linktimeplugin::RegistrarBase<PluginBase>::end(),
[](const auto x) {
(*x)().dosomething();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that empty pair of parentheses un-intuitive. I understand why it's needed, but one would expect the iterator to work as usual, i. e. x->dosomething();, as in the original example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I don't like that form at all either. I noticed that down-casting is needed to get the right type object back.

// plug-in class).
static std::unique_ptr<std::vector<RegistrarBase<BASE>*>>registrars_;
static RegistarColl& registrars() {
static RegistarColl regs;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calls the std::vector ctor which may throw, which would make the RegistrarBase ctor violate its noexcept promise. I think, however, that the static object hidden in a private function is superior to the smart pointer used previously.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. I only considered the allocation in the original code and not push_back which obviously can trigger reallocation which can throw bad_alloc. try-catch is needed then.

return ret;
}

// Use (*begin())() to get BASE object!
static auto begin() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this use of auto supported in C++11 already?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. Haven't considered C++11. Enjoyed C++17 given in the cmake file. I should have remembered the comment was saying it didn't really needed C++17.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad as well because I forgot to change from 17 to 11 in the cmake file. This was originally written for a C++17 application, and I never bothered to test it on older versions until the requirement to use it in 11 came up recently.

linktimeplugin.hpp Outdated Show resolved Hide resolved
main.cpp Outdated
std::cout << "again with iterators\n";
std::for_each(
linktimeplugin::RegistrarBase<PluginBase>::begin(),
linktimeplugin::RegistrarBase<PluginBase>::end(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to the for loop above, this iterator interface is much more complex to use, and the type name needs to be repeated. It would be nice to have something as elegant as the range-for syntax that still didn't have to create the std::vector returned by the plugins() function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Typing it was very painful, I wouldn't want anyone else needing to do it.

// static object, and there's no other way to catch
// it.
RegistrarBase() noexcept {
registrars().push_back(this);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

push_back may throw, violating the noexcept promise.

typo in typedef

Co-authored-by: Wolfram Rösler <[email protected]>
@vaczijani
Copy link
Author

vaczijani commented Jun 16, 2021

I can see the difficulties with the current class hierarchy.
Would it be acceptable to do the downcasting in the RegistrarBase class and leave the Registrar class out?
Like:

#include <iostream>
#include <vector>
#include <algorithm>

namespace linktimeplugin {
// Register class keeps a collection of Plugins
// which you can retrieve either
// - Register<Plugin>::getPlugins()
// or
// - Register<Plugin>::begin(), Register<Plugin>::end()
// Plugin must be a subclass of Register!
// e.g. class PluginInterface : public Register<PluginInterface> {...}
template<class Plugin>
class RegistrarBase {
public:
    RegistrarBase() { registerPlugin(this); }
    // rule of 5
    RegistrarBase(const RegistrarBase&) = delete;
    RegistrarBase(RegistrarBase&&) = delete;
    RegistrarBase& operator=(const RegistrarBase&) = delete;
    RegistrarBase& operator=(RegistrarBase&&) = delete;
    virtual ~RegistrarBase() = default;

    typedef std::vector<Plugin*> Collection;
    typedef typename Collection::const_iterator const_iterator;
    // provide only const data for user
    static const_iterator begin() { return plugins().begin(); }
    static const_iterator end() { return plugins().end(); }

    static const Collection& getPlugins() { return plugins(); }

private:
    void registerPlugin(RegistrarBase<Plugin>* reg) {
        plugins().push_back(static_cast<Plugin*>(reg));
    }

    static Collection& plugins() {
        static Collection singleton;
        return singleton;
    }
};

// convenience macros:
// REGISTER_PLUGIN clearly indicates intent
#define REGISTER_PLUGIN(x) static x x##Instance
// DEFINE_PLUGIN_INTERFACE avoids repetition of plugin class name
#define DEFINE_PLUGIN_INTERFACE(x) class x : public RegistrarBase<x>


// Actual plugin interface defines the methods can be invoked
// on the given plugin. Has to inherit from Register class
// and tell its own type in template parameter.
DEFINE_PLUGIN_INTERFACE(PrinterPlugin) {
public:    
    virtual void print() = 0;
};

// Concrete plugins
class Cat : public PrinterPlugin {
public:
    virtual void print() override {
        std::cout << "meow\n";
    }
};

REGISTER_PLUGIN(Cat);

class Dog : public PrinterPlugin {
public:
    virtual void print() override {
        std::cout << "wuf\n";
    }
};

REGISTER_PLUGIN(Dog);

// A different plugin interface
DEFINE_PLUGIN_INTERFACE(OtherPlugin) {
public:
    virtual void move() = 0;
};

// Concrete plugin
class Snake : public OtherPlugin {
public:
    virtual void move() override {
        std::cout << "slither\n";
    }
};

REGISTER_PLUGIN(Snake);

} // namespace

int main() {
    typedef linktimeplugin::RegistrarBase<linktimeplugin::PrinterPlugin> Printers;
    std::cout << "using collection and range:\n";
    for(const auto p : Printers::getPlugins()) {
        p->print();
    }

    std::cout << "using iterator:\n";
    std::for_each(Printers::begin(), Printers::end(), 
        [](linktimeplugin::PrinterPlugin* p) {
            p->print();
        }
    );

    std::cout << "another type of plugin:\n";
    typedef linktimeplugin::RegistrarBase<linktimeplugin::OtherPlugin> Others;
    std::for_each(Others::begin(), Others::end(),
        [](linktimeplugin::OtherPlugin* p) {
            p->move();
        }
    );
}

Notice that I've let the constructor to throw exception: this can go into try/catch if unacceptable. I would rather let the program to throw exception on that because if extending the vector of pointers fails then we may have severe problem. Obviously I appreciate that you chose not to throw expcetion from the constructor for a very good reason.

@vaczijani
Copy link
Author

Reorganised RegistrarBase and removed Registrar class to make the usage more convenient. Ready for an other review.

@vaczijani
Copy link
Author

I'm not familiar with git. Sorry I don't understand what that confict is. I thought I've done everything I needed to resolve it.

git checkout master
git pull
git checkout iterator-interface
git merge master
git push origin iterator-interface
<Everything up-to-date

@wolframroesler
Copy link

wolframroesler commented Jun 24, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants