Плохая практика использования указателей функций на членов класса 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() неконстантным, потому что это позволило бы все виды других шалостей с возвращаемой ссылкой. Мое решение состоит в том, чтобы добавить новую общедоступную функцию mod_data(...) в 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 (*)(), но это, вероятно, можно решить с помощью шаблонов с переменным числом аргументов.

Этот пример, очевидно, очень истолкован и не очень хорошо реализован, но я думаю (надеюсь?), что этого достаточно, чтобы объяснить, о чем я говорю.

Спасибо!

РЕДАКТИРОВАТЬ: Кажется, есть некоторая путаница в том, в чем заключается вопрос. По сути, это сценарий, о котором я говорю: у вас есть набор классов из какой-то библиотеки, которые вы пытаетесь сохранить в контейнере, и еще одна функция, которая генерирует определенные контейнеры; Теперь вы хотите, чтобы пользователь мог вызывать существующие функции-члены для объектов внутри этих контейнеров, но не изменять фактические объекты (например, при возврате неконстантной ссылки с помощью геттера). Фактическая реализация, вероятно, будет использовать какой-то вариативный шаблон, чтобы быть полезным, но мне нужно подумать над этим еще немного, прежде чем публиковать пример кода.

Короче говоря, я хотел бы ограничить доступ пользователя к членам контейнера только функциями-членами этого члена. Есть ли более простой способ сделать это, или этот способ не работает так, как я собирался?


person Moritz    schedule 14.08.2012    source источник
comment
Шаблон дляmodifyData требуется, поскольку компилятор жалуется на T::*funcptr, так как T не является классом или пространством имен. - это не обязательно, вам просто нужно использовать typename T::*funcptr, а не T::*funcptr.   -  person Griwes    schedule 14.08.2012
comment
Кроме того, не могли бы вы уточнить, в чем здесь вопрос? Мол, есть ли лучший способ, чем специализировать Container для T*? или есть лучший способ, чем сделать тот шаблон‹typename U›?, к которому я обращался выше?   -  person Griwes    schedule 14.08.2012
comment
не может ли пользователь создать свой собственный класс с функцией-членом void f(), привести его к типу вашего класса, а затем его функция будет работать с вашим указателем this?   -  person Gir    schedule 14.08.2012
comment
Спасибо, Гривес! Я отредактирую вопрос, чтобы отразить это. Также постараюсь конкретизировать вопрос, см. выше.   -  person Moritz    schedule 14.08.2012
comment
Основная проблема (если хотите), как я вижу, заключается в том, что вы в основном открываете доступ к интерфейсу публичной функции содержащегося объекта. (По крайней мере, для функций, принимающих пустоту, но останется ли это так?) Единственным преимуществом является предотвращение доступа к публичным членам data, которые не должны существовать в первую очередь (в вашем примере их нет). Разница с открытием непостоянного доступа во многих случаях невелика и, вероятно, не оправдывает обстоятельства.   -  person Peter - Reinstate Monica    schedule 11.04.2016


Ответы (1)


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

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

person Elemental    schedule 14.08.2012
comment
в том, что пользователь может использовать любую функцию void для изменения содержащихся данных: это именно то, что я пытался решить, обеспечив, чтобы переданная функция была членом T. Например, скажем, у меня есть куча классов из какой-то библиотеки, которую я пытаюсь хранить в контейнере и другую функцию, которая генерирует определенные контейнеры; Я хочу, чтобы пользователь мог вызывать существующие функции-члены для объектов внутри этих контейнеров, но не изменять сами объекты. Как я уже сказал, реальная реализация, вероятно, будет использовать какой-то вариативный шаблон, чтобы быть полезным, но мне нужно кое-что... - person Moritz; 14.08.2012
comment
... пора придумать толковый пример. (Черт, 600 символов — это не так много.) Как вы думаете, какой способ защиты данных был бы лучше? - person Moritz; 14.08.2012