Плохая практика использовать указатели функций на члены класса T в качестве параметров в функциях шаблонного класса<T>?
Прежде всего, извините за название. Я не мог сжать то, что я пытаюсь спросить, в одну фразу:(
Я читал этот пост, и он каким-то образом заставил меня задуматься о функциональных указателях. В частности, мне было интересно, почему "плохо" (или, по крайней мере, редко встречается) передавать функции-члены класса в качестве параметров функции, а затем использовать этот указатель на существующий объект в этой функции.
Предположим, у меня есть шаблонный класс "Контейнер", который хранит одну переменную типа T и предоставляет метод для получения константной ссылки на эту переменную.
template<class T>
class Container {
public:
Container(T anObject) {
m_data = anObject;
}
const T& getData() const {
return m_data;
}
private:
T m_data;
};
Теперь я хотел бы иметь возможность выполнять функции-члены T для m_data, но я не хочу делать getData() неконстантным, потому что это могло бы привести ко всем видам другого вреда с возвращенной ссылкой. Мое решение состоит в том, чтобы добавить новую открытую функцию modifyData(...) в Container, которая принимает указатель на функцию-член T в качестве параметра и выполняет ее для m_data; вот так:
// ...
void modifyData( void(typename T::*funcptr)(void) ) {
(m_data.*fptr)();
}
// ...
Как есть, это приведет к сбою и сгоранию, если T является указателем. Для тестирования я только что создал специализированный шаблон для Container<T*>
чтобы решить эту проблему, но я уверен, что будет более элегантный способ.
Очень понятный пример показывает, что это похоже на работу:
// example class to be used with Container
class Data {
public:
Data() {m_count = 0; }
void incrementCount() { m_count++; }
int getCount() const { return m_count; }
private:
int m_count;
};
// ... in main.cpp:
Data dat;
Container<Data*> DCont(dat);
std::cout << cl.getData()->getCount() << std::endl; // outputs 0
DCont.modifyData<Data>(&Data::incrementCount);
std::cout << cl.getData()->getCount() << std::endl; // outputs 1
// compiler catches this:
// DCont.modifyData<SomeOtherClass>(&Data::incrementCount);
// this probably does something bad:
// DCont.modifyData<SomeOtherClass>(&SomeOtherClass::someFunc);
Теперь, инстинктивно, это просто кажется ужасно извращенным способом делать вещи, и я никогда не видел код, который работает так. Но мой вопрос: есть ли причина в производительности / безопасности, почему что-то подобное плохо или это просто считается плохой практикой? Если это "просто" плохая практика, то почему это так?
Очевидные ограничения, о которых я мог подумать, это что-то вроде // DCont.modifyData(&SomeOtherClass::someFunc); может произойти сбой во время выполнения, но я думаю, что это может быть решено путем проверки типа U против T в incrementData(). Кроме того, как есть, modifyData принимает только функции void (*)(), но это, вероятно, может быть решено с помощью шаблонов с переменным числом аргументов.
Этот пример явно очень понятный и не очень хорошо реализован, но я думаю (надеюсь?), Что он достаточно хорош, чтобы объяснить, о чем я говорю.
Спасибо!
РЕДАКТИРОВАТЬ: Кажется, есть некоторая путаница относительно того, что вопрос. По сути, это сценарий, о котором я говорю: у вас есть набор классов из некоторой библиотеки, которые вы пытаетесь сохранить в контейнере, и другая функция, которая генерирует определенные контейнеры; Теперь вы хотите, чтобы пользователь мог вызывать существующие функции-члены для объектов в этих контейнерах, но не изменять реальные объекты (например, при возврате неконстантной ссылки с помощью метода get). Реальная реализация, вероятно, будет использовать какой-то шаблон переменной, чтобы быть полезным, но мне нужно подумать об этом еще немного, прежде чем публиковать пример кода.
Короче говоря, я бы хотел ограничить доступ пользователя к членам контейнера только функциями-членами этого члена. Есть ли более простой способ сделать это, или этот способ не работает так, как я планировал?
1 ответ
У меня нет проблем с вашей архитектурой - я не считаю это плохой практикой. Мне кажется, это довольно трудоемкий способ защиты данных, и он не очень вам помогает, поскольку пользователь может использовать любую пустую функцию для изменения содержащихся данных, что на самом деле не является договором о том, что можно и нельзя изменить.
Я думаю, причина, по которой такая конструкция встречается так редко, в том, что ваши требования и цели класса контейнера необычны.