为什么const-correctness特定于C ++?

时间:2009-09-02 20:53:19

标签: c++ const const-correctness

免责声明:我知道有两个关于const-correctness有用的问题,但是,没有人讨论过如何在C ++ 中使用const-correctness而不是其他编程语言。另外,我我对这些问题的答案不满意。

我现在使用了一些编程语言,而在C ++中让我烦恼的一件事就是const-correctness的概念。在Java,C#,Python,Ruby,Visual Basic等中没有这样的概念,这似乎是C ++非常具体的。

在你推荐我使用C ++ FAQ Lite之前,我已经阅读了它,但这并不能说服我。完全有效,可靠的程序一直用Python编写,没有const关键字或等价物。在Java和C#中,对象可以声明为final(或const),但是没有const成员函数或const函数参数。如果函数不需要修改对象,则它可以采用仅提供对象的读访问权的接口。该技术同样可以在C ++中使用。在我工作的两个真实的C ++系统上,几乎没有使用const,一切都运行良好。因此,对于让const污染代码库的有用性,我还远没有卖掉。

我想知道C ++中是什么让const成为必需,而不是其他编程语言。

到目前为止,我只看到了一个必须使用const 的情况:

#include <iostream>

struct Vector2 {
    int X;
    int Y;
};

void display(/* const */ Vector2& vect) {
    std::cout << vect.X << " " << vect.Y << std::endl;
}

int main() {
    display(Vector2());
}

Visual Studio接受使用const注释掉的编译,但警告C4239使用了非标准扩展。所以,如果你想要传递临时代码,避免副本和保持标准兼容的语法简洁,你必须通过const引用,不管它。不过,这更像是一个怪癖,而不是一个根本原因。

否则,实际上不存在必须使用const的情况,除非与使用const的其他代码连接。康斯坦特对我来说似乎不过是一种自以为是的瘟疫,它会传播到它触及的一切:

  

const在C ++中工作的原因是   因为你可以扔掉它如果你   无法抛弃它,然后是你的世界   会很糟糕如果您声明一个方法   需要一个const Bla,你可以通过   它是一个非常规的Bla。但如果是的话   你的其他方式不能。如果你   声明一个采用a的方法   非const Bla,你不能传递它   const Bla。所以现在你被卡住了。那么你   逐渐需要一个const版本   一切都不是常数,而你   结束了阴影世界。在C ++中你   侥幸逃脱,因为和   C ++中的任何东西都是纯粹可选的   你是否想要这个检查。   你可以打破常量   如果你不喜欢它。

Anders Hejlsberg(C#架构师),CLR Design Choices

14 个答案:

答案 0 :(得分:63)

Const正确性为我能想到的C ++提供了两个显着的优点,其中一个使它非常独特。

  • 它允许可变/不可变数据的普遍概念,而不需要一堆接口。可以注释各个方法是否可以在const对象上运行,并且编译器强制执行此操作。是的,它有时可能很麻烦,但是如果你一直使用它并且不使用const_cast,那么就可变数据与不可变数据的编译器检查安全性。
  • 如果对象或数据项为const,则编译器可以将其放在只读存储器中。这在嵌入式系统中尤为重要。 C ++支持这个;很少有其他语言。这也意味着,在一般情况下,您无法安全地投射const,尽管在实践中您可以在大多数环境中这样做。

C ++不是唯一具有const正确性的语言或类似的东西。 OCaml和Standard ML有一个类似的概念,使用不同的术语 - 几乎所有数据都是不可变的(const),当你想要某些东西是可变的时,你可以使用不同的类型(ref类型)来实现它。所以它在相邻语言中只是C ++的独特之处。

最后,走向另一个方向:曾经有一段时间我想在Java中使用const。 final有时甚至不足以创建明显不可变的数据(尤其是可变数据的不可变视图),并且不想创建接口。查看Java API中的Unmodifiable集合支持,以及它仅在运行时检查是否允许修改const为什么有用的示例(或者至少接口结构应该深入到具有List和MutableList)的事实 - 那里没有理由试图改变不可变结构不能是编译类型错误。

答案 1 :(得分:30)

我认为没有人声称const-correctness是“必要的”。但同样,课程也不是真的有必要,是吗?名称空间,例外情况也是如此......你得到了图片。

Const-correctness有助于在编译时捕获错误,这就是它有用的原因。

答案 2 :(得分:16)

const是一种表达某种东西的方式。如果您认为表达它很重要,那么它在任何语言中都会有用。他们没有这个功能,因为语言设计师没有发现它们有用。如果这个功能在那里,我认为这将是有用的。

我认为它就像Java中的throw规范。如果你喜欢它们,你可能会喜欢其他语言。但是其他语言的设计者并不认为它那么重要。

答案 3 :(得分:13)

你是对的,没有必要保持正确性。您当然可以在没有const关键字的情况下编写所有代码并使其工作正常,就像在Java和Python中一样。

但是如果你这样做,你将不再得到编译器的帮助来检查const违规。编译器在编译时告诉你的错误现在只能在运行时找到,如果有的话,因此需要更长时间来诊断和修复。

因此,试图颠覆或避免使用const-correctness功能只会让你自己从长远来看更加困难。

答案 4 :(得分:11)

嗯,我需要6年才能真正理解,但现在我终于可以回答我自己的问题了。

C ++具有“const-correctness”而Java,C#等没有的原因是C ++仅支持值类型,而这些其他语言仅支持或至少默认为参考类型

让我们看看C#是一种默认为引用类型的语言,它涉及涉及值类型时的不变性。假设您有一个可变值类型,另一个类型具有该类型的只读字段:

struct Vector {
    public int X { get; private set; }
    public int Y { get; private set; }
    public void Add(int x, int y) {
        X += x;
        Y += y;
    }
}

class Foo {
    readonly Vector _v;
    public void Add(int x, int y) => _v.Add(x, y);
    public override string ToString() => $"{_v.X} {_v.Y}";
}

void Main()
{
    var f = new Foo();
    f.Add(3, 4);
    Console.WriteLine(f);
}

这段代码应该做什么?

  1. 无法编译
  2. 打印“3,4”
  3. print“0,0”
  4. 答案是#3。 C#尝试通过调用对象的丢弃副本Add方法来尊重“readonly”关键字。这很奇怪,是的,但它有什么其他选择?如果它调用原始Vector上的方法,则对象将发生更改,从而违反字段的“readonly”。如果它无法编译,那么readonly值类型成员就没用了,因为你不能在它们上面调用任何方法,因为它们可能会改变对象。

    如果我们只能标记在readonly实例上调用哪些方法是安全的......等等,这正是C ++中的const方法!

    C#没有使用const方法,因为我们不使用C#中的值类型;我们只是避免使用可变值类型(并声明它们是“邪恶的”,请参阅12)。

    此外,引用类型不会遇到此问题,因为当您将引用类型变量标记为只读时,引用的只是引用,而不是对象本身。这对于编译器来说非常容易实施,它可以将任何赋值标记为编译错误,除非在初始化时。如果您使用的只是引用类型,并且您的所有字段和变量都是只读的,那么您在任何地方都会以不小的语法成本获得不变性。 F#完全像这样工作。 Java通过不支持用户定义的值类型来避免此问题。

    C ++没有“引用类型”的概念,只有“值类型”(在C#-lingo中);其中一些值类型可以是指针或引用,但与C#中的值类型一样,它们中没有一个拥有它们的存储。如果C ++在其类型上处理“const”,就像C#在值类型上处理“readonly”一样,那就像上面的例子所展示的那样非常混乱,永远不要与复制构造函数进行讨厌的交互。

    因此,C ++不会创建一个丢弃的副本,因为这会产生无穷无尽的痛苦。它并不禁止你在成员上调用任何方法,因为,那么语言就不那么有用了。但它仍然希望有一些“只读”或“常识”的概念。

    C ++尝试通过标记哪些方法可以安全地调用const成员来找到一种中间方式,然后它相信您在标记中忠实且准确,并直接调用原始对象上的方法。这并不完美 - 它很冗长,你可以随意违反常规 - 但它可以说比所有其他选择更好。

答案 5 :(得分:8)

答案 6 :(得分:5)

如果您正在为具有FLASH或ROM数据的嵌入式设备编写程序,那么没有const-correctness就无法生存。它使您能够控制不同类型内存中数据的正确处理。

答案 7 :(得分:4)

您也希望在方法中使用const,以便利用返回值优化。请参阅Scott Meyers更有效的C ++第20项。

答案 8 :(得分:3)

talk and video 中的Herb Sutter解释了const关于线程安全的新内涵。

Constness 可能不是你以前需要担心的事情,但是如果你想编写线程安全的代码你需要了解{{1}的重要性,那么使用C ++ 11 }和const

答案 9 :(得分:2)

在C,Java和C#中,您可以通过查看调用站点来判断是否可以通过函数修改传递的对象:

  • 在Java中你知道它绝对可以。
  • 在C中你知道它只能是'&amp;'或同等物。
  • 在c#中你也需要在呼叫网站上说'ref'。

在C ++中,你通常无法说明这一点,因为非const引用调用看起来与pass-by-value相同。拥有const引用允许您设置和实施C约定。

这会对调用函数的任何代码的可读性产生相当大的影响。这可能足以证明语言功能的合理性。

答案 10 :(得分:2)

  

Anders Hejlsberg(C#架构师):...如果声明一个采用非常量Bla的方法,则不能将它传递给const Bla。所以现在你被卡住了。所以你逐渐需要一个不是const的所有东西的const版本,你最终得到了一个阴影世界。

再次说明:如果你开始在某些方法中使用“const”,你通常会在大多数代码中使用它。但是,代码中const修正的维护(键入,重新编译时缺少某些const等)所花费的时间似乎比修复由于不使用const-correctness而导致的可能(非常罕见)问题的时间要大。因此,在现代语言(如Java,C#,Go等)中缺乏const-correctness支持可能会导致相同代码质量的开发时间略有缩短。

自1999年以来,Java Community Process中存在用于实现const正确性的增强请求票证,但由于上述“const污染”以及兼容性原因而于2005年关闭:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070

虽然C#语言没有const正确性构造,但类似的功能可能会很快出现在.NET Framework的“Microsoft Code Contracts”(库+静态分析工具)中,使用[Pure]和[Immutable]属性:{{3} }

答案 11 :(得分:2)

实际上,不是......不完全是这样。

在其他语言中,尤其是函数或混合语言,如Haskell,D,Rust和Scala,你有可变性的概念:变量可以是可变的,也可以是不可变的,默认情况下通常是不可变的。

这让你(和你的编译器/解释器)更好地理解函数:如果你知道一个函数只接受不可变参数,那么你就知道函数不是那个改变你的变量并导致错误的函数。

C和C ++使用const做类似的事情,除了它是一个不那么坚定的保证:不强制的不变性;调用堆栈中的一个函数可以抛弃constness并改变你的数据,但这会故意违反API契约。因此,意图或最佳实践是使其在其他语言中与不变性一样工作。

所有这一切,C ++ 11现在有一个实际的可变关键字,以及更有限的const关键字。

答案 12 :(得分:1)

C ++中的const关键字(适用于参数和类型声明)试图阻止程序员从大脚趾射击并在整个过程中完成整个过程。

基本思想是将某些内容标记为“无法修改”。无法修改const类型(默认情况下)。 const指针不能指向内存中的新位置。简单,对吧?

嗯,这就是const正确性的来源。以下是使用const时可以找到的一些可能的组合:

一个const变量 意味着不能修改变量名称标记的数据。

指向const变量的指针 意味着可以修改指针,但数据本身不能。

指向变量的const指针 意味着指针不能被修改(指向新的内存位置),但是指针指向的数据可以被修改。

指向const变量的const指针 意味着指针和指向它的数据都不能被修改。

你看到有些事情在那里有些愚蠢吗?这就是为什么当你使用const时,重要的是要正确标记你所在的const。

关键是这只是一个编译时的黑客攻击。标签只是告诉编译器如何解释指令。如果你抛弃const,你可以做任何你想做的事。但是你仍然需要调用具有const要求的方法,并使用适当的类型转换。

答案 13 :(得分:0)

例如,你有一个功能:

void const_print(const char* str)
{
    cout << str << endl;
}

另一种方法

void print(char* str)
{
    cout << str << endl;
}

主要:

int main(int argc, char **argv)
{
    const_print("Hello");
    print("Hello");        // syntax error
}

这是因为“hello”是一个const char指针,(C风格)字符串被放在只读内存中。 但是当程序员知道值不会被改变时,它总体上是有用的。因此得到编译器错误而不是分段错误。 就像在非通缉的作业中一样:

const int a;
int b;
if(a=b) {;} //for mistake

因为左操作数是一个const int。