【问题标题】:C++, Classes, Const, and strange syntaxC++、类、常量和奇怪的语法
【发布时间】:2011-04-13 22:55:50
【问题描述】:

我今天在重读 c++ 入门(第 4 版)——关于成员函数和 const 引用等的部分,我想出了这个奇怪的小程序:

using std::cout;
using std::endl;

class ConstCheater
{
public:
    ConstCheater(int avalue) : ccp(this), value(avalue) {}
    ConstCheater& getccp() const {return *ccp;}
    int value;
private:
    ConstCheater* ccp;
};

int main()
{
    const ConstCheater cc(7); //Initialize the value to 7
    cout << cc.value << endl;
    cc.getccp().value = 4;    //Now setting it to 4, even though it's const!
    cout << cc.value << endl;
    cc.value = 4;             //This is illegal
    return 0;
}

我的问题是 - 为什么 c++ 允许这样的语法?为什么我可以在声明为 const 的类中编辑普通数据成员? const的要点不就是让你不能修改值吗?

【问题讨论】:

  • 您可以绕过 C++ 的大多数安全机制,因为该语言的基础是程序员非常清楚自己在做什么。在我看来,您的程序几乎不像是“意外”。如果你绝对在脚上开枪,那就去做吧。 C++ 不会阻止你。
  • @FredOverflow,在这种情况下很明显,因为他试图让它变得明显,但即使很清楚为什么会发生这种情况,它仍然是一个有趣的概念问题需要解决,因为可以想象这会以一种错综复杂的方式偶然发生。

标签: c++ class reference constants


【解决方案1】:

尽管getccp() 是一个const 方法,但它不承诺您如何处理它返回的引用。该方法本身不会修改对象,因此不会违反规则。

如果它返回一个const ConstCheater&amp;,那就不同了。

正如您的示例所示,const 比仅将其应用于对象要复杂得多。 C++ FAQ 有一个关于const correctness 的部分,特别是it covers the case,您在此处突出显示。

【讨论】:

  • 我不认为常见问题解答涵盖了这种情况……它掩盖了这些功能的实现,从表面上看,这似乎需要在某处使用const_cast
  • 怎么不覆盖呢?他们给出的示例是 const 方法,其声明如下:std::string&amp; name_evil() const; 这与 Schnommus 给出的示例非常相似:ConstCheater&amp; getccp() const 它们都是 const 方法,返回一个非常量引用,允许外部代码修改(可能) 对象的私有成员。
  • @Tony:这里的问题是他设法在不使用 const_cast 的情况下创建了对 const 数据的非 const 引用,这不是你在说的。
  • @Dennis:只有const ConstCheater cc(7);这里创建的对象是const,里面的数据没有明确标记为const。拥有一个 const 对象意味着我们只能调用 const 方法,他这样做了,但是返回的指针/引用(在这种情况下恰好指向同一个对象)没有标记为 const。指针和引用不会从它们指向的对象“继承”常量,这取决于类的接口和内部,以确保遵守 const 契约。
  • @Tony:const 对象的所有成员本身都是 const。所以没有明确的方法来改变cc.value。是的,cc 的 const-ness 不会改变 *ccp 的 const-ness,但反之亦然。尝试在除构造函数之外的任何方法中将ccp 分配给this,您将因此而收到编译时错误。
【解决方案2】:

我会说你标记为正确的 Tony 的答案是不正确的,而 Michael Burr 的答案是正确的。

为了更清楚地表达他所说的(至少对我而言):

有两个可能产生误解的地方:

  1. 隐式 const 的工作方式
  2. 在构造 const 对象期间解释 this 的方式

1。隐式常量

隐式 const(ConstCheater 内部的 constification 当它被制成 const 时)不会将 cc 变成 pointer-to-const 而是 const-pointer,也就是说当你这样做时:

  const ConstCheater cc(7); 

内部来自:

  ConstCheater * ccp;

...到...

  ConstCheater * const ccp;

...而不是...

  const ConstCheater * ccp;    

这可能是意料之中的。

2。 const对象的构造

奇怪的是,this 允许在构造函数中传递给cpp 的初始化程序,因为有人会认为this 应该被视为pointer-to-const,因此不是传递给const-pointer 的有效值。

也就是说人们可能期望:

 ...: ccp(this) ... // expected to fail but doesnt

失败,因为从概念上讲,您可能期望这(在某种程度上)等同于:

 const ConstCheater         cc(7);
 const ConstCheater * const this = &cc; // const-pointer-to-const

因此你会认为:

 ConstCheater * const ccp = this; //expected error!

会失败!但这不是因为显然在构造过程中this 被特殊对待,就好像它是:

 const ConstCheater * this = &cc; 

因此对象在构造过程中实际上不是 const。

我不确定我是否完全理解其中的原因,但 Michael Burr 指出,提供预期行为似乎存在逻辑和技术障碍,因此该标准似乎排除了当前有些奇怪的行为。

我最近问了一个相关的问题:Why does C++ not have a const constructor?,但到目前为止,我还没有真正完全理解为什么它会站不住脚,尽管我认为它会给 C++ 开发人员带来负担,必须定义一个尴尬的 const他们想创建 const 对象的任何类的构造函数。

【讨论】:

  • 回答有点晚了 :) - 但你说的很有道理
【解决方案3】:

允许构造函数修改const对象的值,是的。但如果不是,它还能做什么?

由于构造函数具有这样的访问权限,它可以将其“转发”给其他人或“保存”以供以后使用。当然,这样做可能是个坏主意。

这是一个例子,C++ 的安全机制不会阻止您构建一个格式错误的程序。 C++ 绝非万无一失。所以,小心点!

【讨论】:

    【解决方案4】:

    对象在构造函数完成它们的事情之前不会变为 const。所以this 是一个指向非常量内存的指针,当你存储它时,它会在不久之后发生变化。这就是为什么它首先允许分配,因为你没有做错任何事。这意味着cpp 是一个指向 const 的指针,但编译器并没有意识到这一点。它没有办法;毕竟,您将其声明为非常量。这仍然是未定义的行为,它不是您的编译器真正希望帮助您捕获的类型。

    【讨论】:

    • +1,更具体地说,对象生命周期在构造函数完成之前不会开始,因此构造函数实际上是在查看具有很少定义属性的内存块(§3.8/1,§3.8 /3).
    【解决方案5】:

    真正的问题不是ConstCheater::getccp() 的行为——而是在线上没有错误:

    const ConstCheater cc(7);
    

    它初始化一个非常量指针,应该是一个常量this指针。但是,构造函数不能是const(9.3.2/5,但稍微想一想应该会明白为什么)。因此,允许构造函数使用指向 const 对象(或“即将成为”const 的对象)的指针来初始化非 const 指针。不过,这就是你要开的洞。

    至于为什么允许它,我想标准很难试图关闭这个漏洞,因为它必须枚举构造函数的 this 必须被处理的所有方式 const 和所有构造 const 对象时必须以non-const 处理的方式。这似乎是一项相当艰巨的任务。

    【讨论】:

      【解决方案6】:

      您的对象并非完全不可变:只是您创建的指向它的引用是不变的。

      【讨论】:

      • 该对象不能合法更改,因为它直接声明为const。此程序中的const 引用在哪里?
      • main 他声明一个常量引用为const ConstCheater cc(7);
      • @Tony:那不是引用,而是对象。
      【解决方案7】:

      您所做的const 是对ConstCheater 的引用。 ConstCheater 中的任何内容都没有使 value 成为 const

      【讨论】:

        【解决方案8】:

        const 限定符限制在您的对象上调用非常量方法,因此问题在于您的设计允许您通过 const 方法向成员提供非常量引用。 常见的做法是

              Member& getccp()       {return *member;}
        const Member& getccp() const {return *member;} 
        

        在某些情况下,当您的对象的逻辑常量不受外部修改其成员的影响时,您可以允许

              Member& getccp() const {return *member;}
        

        另一个逻辑和形式常量不同的例子是mutable 成员。 IE。 mutable 可以是在最后一次 const 方法调用时计算的某个术语,如果您在下次调用时获得相同的输入,则可以轻松返回存储的值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-12-06
          • 1970-01-01
          • 2016-08-26
          • 2012-03-14
          • 2013-11-15
          • 1970-01-01
          • 2012-12-17
          • 2012-10-12
          相关资源
          最近更新 更多