D有两种类型的constness: immutable 变量是声明为不可变的变量,并且总是不可变的,而 const 变量只是对象的只读版本
逻辑const 是指函数被标记为 const ,但允许对一个或多个成员变量进行写访问。这种情况的典型用途是用于惰性评估,例如, (在C ++中)
struct Matrix
{
double determinant() const
{
if ( m_dirty )
{
m_determinant = /* expensive calculation */;
m_dirty = false;
}
return m_determinant;
}
void set(int i, int j, double x) { m_dirty = true; ...; }
mutable bool m_dirty;
mutable double m_determinant;
};
此处determinant()
为const
,但仍可以修改m_dirty
和m_determinant
,因为它们被标记为mutable
。
D const(FAQ)表示D2不支持逻辑const,因为它提供的弱保证,这是编写并发程序的一个障碍,并使某些优化更加困难。
我完全理解这个问题,但是如果我们需要逻辑const?
考虑上面使用Matrix
类的情况,但没有缓存(并且需要逻辑const)。还想象一下,这个类在我的代码库中使用,并且主要通过const引用访问。
现在考虑分析已经发现determinant()
函数是代码中的瓶颈,而且它通常被重复访问,其值很少改变,即缓存,如上所述,将是一个完美的优化。
如果没有逻辑const,我怎么能这样做?遍布我的代码库,将const引用更改为非const引用不是一种选择(出于显而易见的原因)。
我有哪些选项(如果有的话)?
答案 0 :(得分:11)
我认为在这里发布recent thread on this topic on the D newsgroup的基本结论是合适的,这样那些不跟踪该列表的人仍然可以得到适当的答案。
D的const不是逻辑const。它是传递性的,完全是常量的。该语言在技术上不支持逻辑const。该语言没有定义任何改变const对象的方法。
实际上,C ++也没有逻辑const。使用mutable
并抛弃const-ness允许你完全绕过const,这样从技术上来说,const实际上并不保证任何东西,除了你没有在const变量上调用任何非const函数。 const函数实际上是const并且不与变量混淆的事实完全按惯例保持在一起。现在,大多数程序员都不会左右摆放const-ness并使一切变得可变,所以在实践中它非常有用,但它不仅可以完全绕过,而且语言特别为你提供了定义的方法。 。在C ++ mutable
中,转换const是由语言定义和支持的。
D不这样做。 D的const实际上是const。在一个变量上抛弃const然后改变它是未定义的。没有可变的。 D的const有真正的保证(只要你不做任何未定义的东西,比如抛弃某些东西然后变异)。这很重要,不仅因为D的编译器保证比C ++的保证强得多,而且因为不可变的变量不能以任何形式或形式被改变。它们可能处于只读内存中,谁知道如果你试图抛弃不变性并改变这样一个变量会发生什么可怕的事情(段错误可能是最好的事情)。并且因为const变量实际上可以引用不可变数据,所以抛出const来改变变量或允许const变量以某种方式被改变将是 bad ,至少可以说。所以,语言不允许这样做。
现在,作为BCS points out,D是一种务实的语言。你可以抛弃const,此时你可以改变变量。因此,例如,您可以使用一个变量来缓存const函数的返回值(如果对象的状态发生变化,可能会使该缓存无效)并抛弃const来更改它。只要有问题的变量实际上不可变,它就会起作用。但是,这是 undefined 行为。一旦你这样做,你就独自一人。你绕过了类型系统和编译器的保证。 您是负责确保您不在不可变对象上执行此操作或以其他方式搞砸编译器通常保证的内容的人。所以,如果你需要这样做,你可以,但是你正在走进狂野的西部,并且由你来确保你不会改变你不应该做的事情。
假设只要变量实际上不引用不可变数据,抛弃const就会起作用,可以创建一个Mutable
模板来基本上获得mutable
在C ++中给你的东西(所以,它会为你抛弃常数。在他的回答中he_the_great gives an example这样的模板。但是使用这样的模板仍然是未定义的行为。在一个实际上不可变的对象上使用它会导致问题。 你,程序员,必须确保它已正确使用。
所以,D在技术上可以通过抛弃const来获得逻辑const,但是为了做到这一点,你必须绕过类型系统超出编译器所保证的范围,你必须确保你不要不要滥用它并改变不应该/不能改变的变量,否则你的代码会有问题 - 段错误很可能是其中最少的。
编辑:我忘了提及不打破类型系统的建议解决方案。只要您愿意放弃纯度,就可以使用某种变量的全局变量(无论是在模块范围,类变量还是结构变量)来保存缓存值。 const函数可以自由地使用和改变全局变量,因此它可以用来代替缺少的mutable
。然而,这确实意味着功能不能是纯粹的,这也可能是一个大问题。然而,这是一种让const函数仍然能够在不破坏类型系统的情况下改变它所需的数据的方法。
答案 1 :(得分:4)
我多年没有接触过D2,所以你可能想仔细检查我说的话。 :)
我不确定你真的有什么好的选择。 D的const和immutable明显强于C / C ++,因此抛弃它们不是一种选择。您明确排除了在代码中更改const的用法。
您可以将操作结果缓存在键入矩阵值本身的全局哈希表中。这适用于const / immutable的任何组合。当然,问题在于D没有世界上最快的哈希表,并且计算哈希值可能很慢。也许在创建矩阵时预先计算哈希值。
另一种选择是在价值变化时急切地计算决定因素。
除此之外,我想不出别的什么。实际上,问题是你要求编译器用const保护你,然后试图打破它。 “正确”的解决方案可能就是不使用const。 :P
答案 2 :(得分:1)
作为一种实用的语言,如果你真的需要,D有能力抛弃const。我认为以下内容应该有效:
class M {
bool set;
real val;
real D() const {
if(!set) {
M m = cast(M)this;
m.val = this.some_fn();
m.set = true;
}
return this.val;
}
}
答案 3 :(得分:0)
我强烈建议BCS的答案,因为只要不创建不可变/ const矩阵,它就是简单而安全的。
另一个有助于使甚至不可变/ const对象保持有效的选项是Mutable Template。或者至少那是意图。有关于这些担忧的评论。
使用此模板,需要对引用的类型进行修改,而不是对const函数中的值进行修改。这意味着使用指针并且需要为每个Matrix分配空间。这也使得创建一个不会发生段错误的不可变/ const矩阵变得更加困难,也许有一种方法可以很好地完成它,但我只知道一个类。
struct Matrix
{
double determinant() const
{
if ( *m_dirty )
{
*m_determinant = 646.363; /* expensive calculation */;
*m_dirty = false;
}
return *m_determinant;
}
void set(int i, int j, double x) { *m_dirty = true; }
Mutable!(bool*) m_dirty;
Mutable!(double*) m_determinant;
};
答案 4 :(得分:0)
要在D中模仿C ++中的逻辑const
,您可以对类使用继承:
class ConstMatrix
{
public:
double det() { // not marked as const!
/* ... caching code ... */
}
/* ... the rest of the logically const interface ... */
}
class Matrix : ConstMatrix
{
public:
void set( int row, int col, double val ) {
/* ... */
}
/* ... the non-logically const interface ... */
}
在ConstMatrix
类实现中,除非将const
限定符放在函数签名上,否则不会进行任何编译器检查。但是,如果将ConstMatrix
用于逻辑常数矩阵,则将获得客户端代码的const正确性。
对于结构,您可以使用alias
技术来完成相同的操作:
struct ConstMatrix
{
/* logically const bla bla blub */
}
struct Matrix
{
public:
alias m this;
/* non-const bla bla blub */
private:
ConstMatrix m;
}
对于类类型,您可以通过以下方式构建具有逻辑const正确性的其他class
es或struct
:
class ConstBiggerClass
{
private:
ConstMatrix m;
}
class BiggerClass : ConstBiggerClass
{
private:
Matrix m;
}
这样编译器会为您检查const
的正确性。但是,您需要一个额外的数据成员。对于class
类型中的class
类型成员,另一种方法是提供一个成员函数,该函数返回具有正确constness的数据成员:
class ConstBiggerClass
{
public:
void someLogicallyConstFunction( /*...*/ ) { /* ... */ }
protected:
abstract ConstMatrix getMatrix();
}
class BiggerClass : ConstBiggerClass
{
public:
void someMutableFunction( /*...*/ ) { /*...*/ }
protected:
Matrix getMatrix() { return m; }
private:
Matrix m;
}