Это слишком непостоянно, чтобы const?

В принципе, у меня следующая ситуация. Замечания: 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_;
};

Мысли?

8

Решение

Я думаю, что это неправильное использование 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.

6

Другие решения

Вместо того, чтобы скрывать промежуточный объект внутри объекта 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_;
};
4

Чтобы ответить на ваш вопрос напрямую.
Если функция 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 разумно
В противном случае, это, вероятно, неправильно.

Надеюсь это поможет

1

Отказ от ответственности: я не защищаю использование 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 от класса, но все же сохраните конструктор и установщик, которые ссылаются на него.

0

Я бы сказал, что ваше использование 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 в этом случае, даже если вы используете класс только из одного потока.

0

Учитывая, что вы не можете проверить input_data для эквивалентности вы могли бы взять криптографический хеш и использовать его для сравнения. Это исключило бы необходимость reuse_intermediate флаг.

0

mutable используется для переопределения константности переменной или члена данных.
Пример:

 Class A
{
public:
int m;
}

void foo(const A& a);

В приведенном выше примере функция foo может быть изменена a.m даже если вы передаете постоянную ссылку.
В вашем примере я не думаю, что вам нужно использовать mutable — это может привести к очень плохим проектам — так как вы будете писать в этот буфер.

0
По вопросам рекламы [email protected]