Контейнер определенного типа-уникальные элементы

#c

#c

Вопрос:

Я признаю, что заголовок вопросов немного расплывчатый, поэтому спасибо, что хотя бы прочитали его 😉

Моя ситуация такова: у меня есть набор классов CommandA , CommandB , … производных от общего чисто абстрактного базового класса ICommand . Теперь мне нужно хранить экземпляры этих производных классов в каком-то контейнере, но с тем условием, что только один из каждого производного типа должен быть разрешен внутри контейнера в любое время. Вместо этого, когда элемент уже существующего производного типа вставляется в коллекцию, произойдет замена существующего экземпляра на новый.

Кроме того, существует необходимость удалить элемент из коллекции на основе его типа.

Я предполагаю, что для этого потребуется какая-то идентификация типа во время выполнения. Я исключил идентификацию типа среды выполнения, предоставляемую компилятором C , потому что в какой-то момент нам может потребоваться скомпилировать проект на (пока неизвестных) старых компиляторах. Таким образом, большинство остроумных шаблонных трюков, вероятно, также выходят из игры. Но, честно говоря, я все еще был бы очень признателен за то, чтобы не присваивать некоторый числовой идентификатор моим производным классам вручную…

Я благодарен за каждый намек на проблему.

Заранее большое спасибо,

Арне

Комментарии:

1.Ресурсы: ciaranm.wordpress.com/2010/05/24/… , docs.wxwidgets.org/stable/wx_rttimacros.html

2. Знаете ли вы каждый из производных Command типов во время компиляции?

3. @ildjarn Я на самом деле так и делаю. Кроме того, все классы команд находятся исключительно внутри «моего» модуля.

4. Тогда это можно будет сделать полностью без RTTI. Я опубликую ответ через несколько минут.

Ответ №1:

Если вы не можете использовать template или RTTI, вы можете сделать что-то вроде этого

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



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


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

Вы можете использовать адрес статической переменной для каждого класса в качестве их идентификатора типа

Комментарии:

1. Это довольно безболезненный способ генерировать идентификатор — мне это нравится!

2. Хороший трюк! Однако я обсудлю это со своей командой. Лучше, чтобы все поняли заранее..

Ответ №2:

Добавьте в ICommand функцию, которая возвращает идентификатор для производного класса. Это не обязательно должен быть числовой идентификатор, это может быть строка, GUID или что-нибудь еще, что вам удобно.

Используйте std::map для хранения указателей на объекты класса, используя идентификатор в качестве ключа.

Ответ №3:

Что не так с числовым идентификатором? Создайте enum в базовом классе вместе с членом, который сохранял бы значение перечисления для каждого объекта. Затем сделайте так, чтобы каждый конструктор подкласса присваивал члену enum соответствующее значение. При необходимости напишите operator< и operator== , используя значения enum, и используйте std::set a в качестве контейнера.

Ответ №4:

Если вы не используете множественное наследование (вы не должны), вы можете получить доступ к первым 4/8 байтам вашего объекта, чтобы получить vfptr (указатель на таблицу виртуальных функций). Он уникален для каждого типа объекта.

Комментарии:

1. Это было бы очень зависящим от компилятора решением. Слишком хрупкий для меня.

2. @Mark: Назовите один компилятор C производственного качества, который не хранит vfptr в первых 4/8 байтах для одиночного наследования.

3. @John, если задействовано множественное наследование, таблица vptr может очень быстро усложниться. Просто необходимость знать, скомпилированы ли вы для 32-разрядной или 64-разрядной версии, делает это сложным. У меня нет для вас контрпримеров, но если есть методы получше, я бы использовал их из принципа.

4. @Mark: конечно, именно поэтому я заметил «если вы не используете множественное наследование». Для одиночного наследования это хороший, надежный метод. Если вы боитесь таких «взломов» или режима 32/64 бит, то вам не следует использовать C / C и вместо этого придерживаться какого-либо управляемого языка.

5. @John : Значит, нам больше не разрешается писать переносимый C ? Почему мне не сообщили?

Ответ №5:

Общие рекомендации:

  • Если вы не можете использовать RTTI, то вы можете идентифицировать классы, добавив статический метод, который возвращает имя класса (или любой другой тип, который может использоваться в качестве идентификации).
  • std::set Контейнер может использоваться для хранения уникальных объектов. Обычно он сравнивает по значению сохраненного типа (в нашем случае он сравнивал бы командные объекты по значению указателя).
  • std::set Также можно использовать пользовательский компаратор. Нам нужен такой, который сравнивает объекты по имени их класса.

Вот пример рабочего кода:

 #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 amp; 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(amp;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;
}
  

Ответ №6:

Поскольку (согласно вашему комментарию) вы заранее знаете все ICommand производные, это может быть тривиально реализовано с помощью Boost.Слияние:

 #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 CommandTamp; 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 constamp; 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
    CommandAamp; 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
    CommandBamp; b2 = command_instance_tracker::get_instance<CommandB>();
}
  

Обратите внимание, что этот подход вообще не использует RTTI или полиморфизм — фактически, не требуется, чтобы Command типы даже производились от общего базового класса. Все, что вам нужно сделать, это создать записи для каждого Command типа в command_instance_tracker::command_set_t .

Не стесняйтесь спрашивать, если у вас есть какие-либо вопросы.

Комментарии:

1. Что ж, это выглядит интересно; большое спасибо — boost еще не используется, и я сомневаюсь, что он будет включен, но, возможно, я смогу взять соответствующие части.