开发者

Container of type-unique items

I recognize the questions title is a bit vague, so thanks for at least reading it ;-)

My situation is this: I have got a set of classes CommandA, CommandB, ... derived from a common pure abstract base class ICommand. Now I need to keep instances of those derived classes in some kind of container but with the restirction that only one of each derived type should be allowed inside the container at any time. Instead when an item of already present derived type is inserted into the collection replacing the existing instance with the new one would occur.

Furthermore there is the need to remove a item from the collection based on its type.

I assume this would require some sort of type-identification at runtime. I have ruled out Runtime-Type-Identification as provided by the C++ compiler because we might at some point required to compile the project on (yet unknown) older compilers. Most witty template tricks thus probably also out of the game. But frankly I still would highly appreciate to not assign some numerical identfier to my derived classes by hand...

I am grateful for every hint on t开发者_运维百科he issue.

Thanks alot in advance,

Arne


If you cannot use template or RTTI you can do something like this

          class ICommand
    {
        virtual void *getStaticId() = 0;
    }



    int bar;
    void* CommandA::getStaticId()
    {
        return &bar;
    }


    int foo;
    void* CommandB::getStaticId()
    {
        return &foor;
    }

You can use the address of static variable per class as their typeid


Add a function to ICommand that returns an identifier for the derived class. It need not be a numeric identifier, it could be a string or a GUID or anything else that is convenient for you.

Use a std::map to contain pointers to the class objects, using the identifier as the key.


What's wrong with a numeric identifier? Make an enum in the base class, along with a member that stored the value of the enum for every object. Then, make every subclass constructor set the enum member to the appropriate value. Optionally write an operator< and operator== using the enum values and use a std::set as a container.


Unless you are using multiple inheritance (you shouldn't), you can access first 4/8 bytes of your object to obtain vfptr (virtual functions table pointer). It's unique for every object type.


General guidelines:

  • If you can't use RTTI then you can identify classes by adding a static method that returns the class name (or any other type that can be used as identification).
  • The std::set container can be used to store unique objects. Normally it compares by value of the stored type (in our it would compare Command objects by pointer value).
  • The std::set can also use a custom comparator. We need one that compares the objects by their class name.

Here's a working code sample:

#include <boost/bind.hpp>
#include <algorithm>
#include <iostream>
#include <set>
#include <stdexcept>
#include <string>

class ICommand
{
public:
    ICommand(const char * inClassName) : mClassName(inClassName) { }

    virtual ~ICommand() {}

    const std::string & getClassName() const { return mClassName; }

private:
    std::string mClassName;
};


class CommandA : public ICommand
{
public:
    static const char * ClassName() { return "CommandA"; }

    CommandA() : ICommand(ClassName()) { }
};


class CommandB : public ICommand
{
public:
    static const char * ClassName() { return "CommandB"; }

    CommandB() : ICommand(ClassName()) { }
};


struct Comparator
{
    bool operator()(const ICommand * lhs, const ICommand * rhs) const
    { return lhs->getClassName() < rhs->getClassName(); }
};


int main()
{
    typedef std::set<ICommand*, Comparator> Commands;
    Commands commands;

    // Add A
    commands.insert(new CommandA);
    std::cout << "commands.size after adding CommandA: " << commands.size() << std::endl;

    // Add A again, overwrites the first A
    commands.insert(new CommandA);
    std::cout << "commands.size after adding a second CommandA: " << commands.size() << std::endl;

    // Add B
    commands.insert(new CommandB);
    std::cout << "commands.size after adding CommandB: " << commands.size() << std::endl;

    // Find B
    Commands::iterator it = std::find_if(commands.begin(), commands.end(), boost::bind(&ICommand::getClassName, _1) == CommandB::ClassName());
    if (it == commands.end())
    {
        throw std::logic_error("Could not find CommandB in the set.");
    }

    // Print found object name
    ICommand * theFoundCommand = *it;
    std::cout << "Found a command, it's name is: " << theFoundCommand->getClassName() << std::endl;

    // Erase B
    commands.erase(it);

    std::cout << "commands.size after removing CommandB: " << commands.size() << std::endl;

    return 0;
}


Since (according to your comment) you know all the ICommand derivatives ahead of time, this can be trivially implemented using Boost.Fusion:

#include <stdexcept>
#include <boost/optional.hpp>
#include <boost/fusion/include/set.hpp>
#include <boost/fusion/include/at_key.hpp>

// stub ICommand and inheritance chain
struct ICommand { virtual ~ICommand() = 0; };
ICommand::~ICommand() { }
struct CommandA : ICommand { ~CommandA() { } };
struct CommandB : ICommand { ~CommandB() { } };
struct CommandC : ICommand { ~CommandC() { } };

// actual implementation, rename as you see fit
class command_instance_tracker
{
    typedef boost::fusion::set<
        boost::optional<CommandA>,
        boost::optional<CommandB>,
        boost::optional<CommandC>
    > command_set_t;

    static command_set_t command_set_;

public:
    template<typename CommandT>
    static CommandT& get_instance()
    {
        using boost::fusion::at_key;
        using boost::optional;
        if (!at_key<optional<CommandT> >(command_set_))
            throw std::runtime_error("no instance for specified command type");
        return *at_key<optional<CommandT> >(command_set_);
    }

    template<typename CommandT>
    static void set_instance(CommandT const& instance)
    {
        using boost::fusion::at_key;
        using boost::optional;
        at_key<optional<CommandT> >(command_set_) = instance;
    }
};
command_instance_tracker::command_set_t command_instance_tracker::command_set_;

// example of usage
int main()
{
    // throws runtime_error, as CommandA instance was never set
    CommandA& a = command_instance_tracker::get_instance<CommandA>();

    {
        CommandB b1;
        // stores the CommandB instance
        command_instance_tracker::set_instance(b1);
    }

    // gets stored CommandB instance, which was copied from b1
    CommandB& b2 = command_instance_tracker::get_instance<CommandB>();
}

Note that this approach makes no use of RTTI or polymorphism whatsoever -- in fact, there's no requirement that the Command types even derive from a common base class. All you need to do is create entries for each Command type in command_instance_tracker::command_set_t.

Feel free to ask if you have any questions.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜