基本上,我有以下情况。注意: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_;
};
因此尝试const正确性时,middle_buffer_永远不会暴露,因此它适合使用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_;
};
思想?
答案 0 :(得分:6)
我认为这是对mutable
的不当使用。根据我的经验,mutable
用于辅助私有成员变量,这些变量本质上不能被声明为const,但不会修改"概念性常量"公共接口。
以Mutex成员变量和线程安全的getter'为例:
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
(在这种情况下是守卫)的数据确实会改变(它被锁定和解锁)但这对constness没有影响从用户的角度来看。 最终,尽管存在可变的互斥锁,但您仍然无法更改const data_成员变量,编译器会强制执行此 。
在你的情况下,你真的希望intermediate_buffer是const,但你通过声明它是可变的来明确地告诉编译器它不是。结果是可以更改数据,编译器无法对其进行操作 。
看到区别?
如果您真的希望接口符合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_;
};
现在,用户的责任真正并由编译器强制执行,无论评论说什么,也不使用任何mutable。如果他们知道input_data已经改变,他们必须创建一个新的,最好是const。
答案 1 :(得分:4)
不要将中间对象隐藏在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_;
};
答案 2 :(得分:1)
直接回答您的问题。 如果函数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);
由于foo为const
,因此用户希望结果相同。
如果是这种情况,那么制作缓冲区mutable
是合理的。
否则,这可能是错误的。
希望这有帮助
答案 3 :(得分:0)
免责声明:我并不主张在我的回答中使用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
,但仍保留构造函数和引用它的setter。
答案 4 :(得分: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
函数,因此它不应该对从thread1调用bar
的输出产生任何影响 - 但是如果时机正确(或错误!)它会 - 并且由于这些原因,我说在这种情况下使用mutable
制作foo
const
是错误的,即使你是只使用单个线程中的类。
答案 5 :(得分:0)
鉴于您无法检查input_data
的等效性,您可以对其进行加密哈希并将其用于比较。这将消除对reuse_intermediate
标志的需要。
答案 6 :(得分:0)
mutable用于覆盖变量或数据成员的常量。 例如:
Class A
{
public:
int m;
}
void foo(const A& a);
在上面的例子中,即使你传递了一个const引用,也允许函数foo修改a.m
。
在您的示例中,我认为您不需要使用可变 - 它可能导致非常糟糕的设计 - 因为您将写入该缓冲区。