#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 еще не используется, и я сомневаюсь, что он будет включен, но, возможно, я смогу взять соответствующие части.