C++ 这对常量来说是不是太不可变了?
基本上,我有以下情况。注意:C++ 这对常量来说是不是太不可变了?,c++,constants,mutable,C++,Constants,Mutable,基本上,我有以下情况。注意: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 explic
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_;
};
因此,为了确保常量的正确性,中间缓冲区从不公开,因此它符合使用可变变量的定义。如果我从未重用过这个缓冲区,或者在使用缓存值之前检查了相同的输入数据,那么故事就到此结束了,但是由于重用中间层,我觉得我已经将它公开了一半,所以我不确定下面的内容是否有意义
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_;
};
想法?免责声明:我的回答不是在提倡使用void*
,我希望这只是为了演示,而您实际上不需要自己使用它
如果在重用相同的输入数据时可以节省大量计算,则将input\u 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;
};
显然,这只是一个大纲,没有更多关于什么是输入数据
,中间缓冲区
和什么东西
的细节,或者它们是如何使用或共享的,很多细节都会丢失。我肯定会从实现中删除指针并使用std::vector
。这尤其适用于输入\ u数据
,您希望在其中存储传入缓冲区的副本。考虑:
A a(input);
//modifiy input
a.foo();
使用不匹配的输入数据/中间缓冲区对可能会得到错误的结果
还要注意的是,如果你不需要为foo
和bar
实际使用input_data
,那么你可以从类中删除void*input_data
,但仍然保留引用它的构造函数和setter。我想说,你使用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
}
A a(some_big_data);
a.bar(some_big_data, true);
由于thread2正在调用一个常量
函数,因此它不应该对从thread1调用条
的输出产生任何影响-但是如果时间是正确的(或错误的!),它会-由于这些原因,我认为在这种情况下,使用可变
使foo
常量
是错误的,即使您仅从单个线程使用该类。我认为这是对mutable
的不当使用。根据我的经验,mutable
用于辅助私有成员变量,这些变量本质上不能声明为常量,但不会修改公共接口的“概念常量”
以互斥体成员变量和“线程安全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_uuu成员变量,编译器将强制执行此操作
在您的例子中,您确实希望中间_缓冲区是常量,但您通过声明它是可变的来明确告诉编译器它不是常量。结果是,您可以更改数据,而编译器对此无能为力
看到区别了吗
如果您真的希望接口符合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。如果他们知道输入数据已经更改,他们将必须创建一个新的,最好是常量 鉴于您无法检查输入数据的等效性,您可能需要对其进行加密哈希,并使用该哈希进行比较。这将消除对reuse\u intermediate
标志的需要。不要将中间对象隐藏在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,那么在任何时候调用它都不会改变下一个操作的结果
例如:
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
}
A a(some_big_data);
a.bar(some_big_data, true);
应给出与完全相同的结果(不包括性能差异)
由于foo是constconst
,用户希望结果是相同的。
如果是这种情况,那么使缓冲区可更改是合理的。
否则,这可能是错误的
希望这有助于使用mutable覆盖变量或数据成员的常量。
例如:
在上面的示例中,函数foo将被允许修改a.m
,即使您传递了常量引用。
在您的示例中,我认为您不需要使用mutable—它可能会导致非常糟糕的设计—因为您将写入该缓冲区。mutable
似乎是合理的。但也许只是将其重命名为bar(void*input\u data,bool fast)
,而不是暗示存在中间层。如果不想让用户伤害自己,也可以将其设置为private
而不是public
。使用t
而不是void
会更清晰void*
具有特殊的语义。根据该参数,您永远不应该使用mutable或mutex锁定任何使用mutable的函数调用。例如,我要说的是,在大多数情况下,mutable
mutex不会改变运行函数的实际结果(除非在计算或类似情况下使用函数中花费的时间,在这种情况下,静音