Плохая практика использовать указатели функций на члены класса 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 ответ

Решение

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

Я думаю, причина, по которой такая конструкция встречается так редко, в том, что ваши требования и цели класса контейнера необычны.

Другие вопросы по тегам