В принципе, у меня следующая ситуация. Замечания: void*
используется для обозначения произвольных данных, они строго типизированы в реальном приложении.
class A
{
public:
//uses intermediate buffer but doesn't change outward behavior
//intermediate buffer is expensive to fill
void foo(void* input_data);
//uses intermediate buffer and DOES explicitly change something internally
//intermediate buffer is expensive to fill
void bar(void* input_data);
//if input_data hasn't changed since foo, we can totally reuse what happened in foo
//I cannot check if the data is equal quickly, so I allow the user to pass in the
//assertion (put the honerous on them, explicitly tell them in the comments
//that this is dangerous to do, ect)
void bar(void* input_data, bool reuse_intermediate);
private:
void* intermediate_buffer_;
void* something_;
};
Таким образом, пытаясь получить правильную константу, Middle_buffer_ никогда не раскрывается, так что это sortof соответствует определению использования mutable
переменная. Если я никогда не использовал этот буфер повторно или проверял наличие равных input_data перед использованием кэшированных значений, это было бы концом истории, но из-за reuse_intermediate я чувствую, что наполовину выставляю его, поэтому я не уверен, или нет, следующее имеет смысл.
class A
{
public:
//uses intermediate buffer but doesn't change something
//intermediate buffer is expensive to fill
void foo(void* input_data) const;
//uses intermediate buffer and DOES explicitly change something internally
//intermediate buffer is expensive to fill
void bar(void* input_data);
//if input_data hasn't changed since foo, we can totally reuse what happened in foo
//I cannot check if the data is equal quickly, so I allow the user to pass in the
//assertion (put the honerous on them, explicitly tell them in the comments
//that this is dangerous to do, ect)
void bar(void* input_data, bool reuse_intermediate);
//an example of where this would save a bunch of computation, though
//cases outside the class can also happen
void foobar(void* input_data)
{
foo(input_data);
bar(input_data,true);
}
private:
mutable void* intermediate_buffer_;
void* something_;
};
Мысли?
Я думаю, что это неправильное использование mutable
, По моему опыту mutable
используется для вспомогательных закрытых переменных-членов, которые по своей природе не могут быть объявлены как const, но не изменяют «концептуальную константность» открытого интерфейса.
Возьмем, например, переменную-член Mutex и «поточно-безопасный метод получения»:
class Getter {
public:
Getter( int d, Mutex & m ) : guard_( m ), data_( d ) { };
int get( ) const { Lock l(guard_); return data_; };
private:
mutable Mutex guard_;
const int data_;
};
Главное здесь то, что данные объявлены mutable
(в этом случае охранник) меняется (он заблокирован и разблокирован), но это не влияет на константность с точки зрения пользователей. В конечном счете, несмотря на изменчивый Mutex, вы все еще не можете изменить переменную-член const data_ и компилятор обеспечивает это.
В вашем случае вы действительно хотите, чтобы промежуточный_буфер был постоянным, но вы явно указываете компилятору это не объявив его изменчивым. В результате вы может изменить данные и компилятор ничего не может с этим поделать.
Увидеть разницу?
Если вы действительно хотите, чтобы интерфейс соответствовал соглашению const, сделайте его явным через что-то вроде этого:
class A {
public:
A( void* input_data );// I assume this deep copies.
void foo() const;
void bar();
private:
const void* intermediate_buffer_;
void* something_;
};
Теперь бремя действительно на пользователя и применяется компилятором независимо от того, что говорится в комментариях и без использования изменяемого. Если они знают, что input_data изменилась, им придется создать новый, предпочтительно const.
Вместо того, чтобы скрывать промежуточный объект внутри объекта const, выставьте его и позвольте foo
а также bar
вернуть копию. Вы очень четко указываете, что промежуточный объект используется совместно, и предоставляете новую возможность хранить более одного под рукой. Если вы хотите скрыть детали реализации, вы можете открыть пустой класс и сделать промежуточный объект дочерним по отношению к этому базовому классу.
class A
{
public:
class Intermediate
{
//empty
};
//uses intermediate buffer but doesn't change outward behavior
//intermediate buffer is expensive to fill
//if cache_ptr is NULL the intermediate buffer is discarded
void foo(void* input_data, Intermediate** cache_ptr = NULL) const;
//uses intermediate buffer and DOES explicitly change something internally
//intermediate buffer is expensive to fill
void bar(void* input_data, Intermediate** cache_ptr = NULL);
private:
class IntermediateImpl : public Intermediate
{
//...
};
void* something_;
};
Чтобы ответить на ваш вопрос напрямую.
Если функция foo является const, то ее вызов в любое время никогда не должен изменять результат следующих операций.
например:
A a(some_big_data);
a.bar(some_big_data, true);
должен давать точно такой же результат, как (исключая различия в производительности)
A a(some_big_data);
a.foo(some_different_big_data);
a.bar(some_big_data, true);
Так как фу это const
пользователи будут ожидать, что результат будет таким же.
Если это так, то создание буфера mutable
разумно
В противном случае, это, вероятно, неправильно.
Надеюсь это поможет
Отказ от ответственности: я не защищаю использование void*
с моим ответом, я надеюсь, что это было просто для демонстрационных целей, и что вам на самом деле не нужно использовать его самостоятельно.
Если при повторном использовании одних и тех же входных данных можно сохранить много вычислений, input_data
переменная-член.
class A
{
public:
A(void * input_data)
{
set_input_data(input_data);
}
//Note, expensive operation
void set_input_data(void* input_data)
{
this->input_data = input_data;
fill_intermediate_buffer();
}
void foo() const;
void bar() const;
private:
void * input_data;
void * intermediate_buffer;
void * something;
};
Это просто набросок, очевидно, без более подробной информации о том, что input_data
, intermediate_buffer
а также something
или как они привыкли или поделились, многие детали будут отсутствовать. Я бы определенно отбросил указатели от реализации и использования std::vector
, Это особенно верно в отношении input_data
, где вы хотите сохранить копию переданного буфера. Рассматривать:
A a(input);
//modifiy input
a.foo();
Вы, вероятно, получите неправильный результат от использования несоответствующего input_data/intermediate_buffer
пара.
Также обратите внимание, что если вам не нужно input_data
за foo
а также bar
тогда вы можете бросить void* input_data
от класса, но все же сохраните конструктор и установщик, которые ссылаются на него.
Я бы сказал, что ваше использование mutable
действительно имеет смысл — но это неверно и, возможно, опасно. Если вы делаете функцию const
это должно быть просто так.
Представьте, что вы используете свой класс в многопоточной программе с несколькими потоками, работающими в одном экземпляре класса, например:
thread1:
while(1) {
(...) //do some work
sharedA->foo(thread1Data);
sharedA->bar(thread1Data, true);
(...) //do some more work
sharedA->bar(thread1Data, true);
(...) //do even more work
}
thread2:
while(1) {
(...) //do some different work, maybe wait for an event
sharedA->foo(thread2Data);
(...) //sleep for a bit
}
Поскольку thread2 вызывает const
функция, она не должна иметь никакого влияния на результат вызова bar
из потока 1 — но если время выбрано правильно (или неправильно!), оно будет — и по этим причинам я бы сказал, что это неправильно использовать mutable
делать foo
const
в этом случае, даже если вы используете класс только из одного потока.
Учитывая, что вы не можете проверить input_data
для эквивалентности вы могли бы взять криптографический хеш и использовать его для сравнения. Это исключило бы необходимость reuse_intermediate
флаг.
mutable используется для переопределения константности переменной или члена данных.
Пример:
Class A
{
public:
int m;
}
void foo(const A& a);
В приведенном выше примере функция foo может быть изменена a.m
даже если вы передаете постоянную ссылку.
В вашем примере я не думаю, что вам нужно использовать mutable — это может привести к очень плохим проектам — так как вы будете писать в этот буфер.