【问题标题】:const method modifies object using referenceconst 方法使用引用修改对象
【发布时间】:2021-09-02 20:59:12
【问题描述】:

以下代码调用const 方法,传递对成员的引用,然后对其进行修改。

#include <iostream>

struct A {
  int i;
  A(int _x) : i(_x) { }
  void calc(int& j, const int value) const { j = value; }
  void set1() { calc(i, 1); }
};

int main()
{
  A a(3);
  std::cout << a.i << std::endl;
  a.set1();
  std::cout << a.i << std::endl;

  return 0;
}

代码使用 gcc 6.4.0 和 clang 5.0.2 编译,没有警告。

代码合法吗? 当从 non-const 方法调用时,const 方法 calc 能够修改对象。

【问题讨论】:

  • 除非您的对象不是const,否则const 说明符没有任何区别。如果aconst,您将无法调用A::set1
  • void calc 尝试i = value;。然后你会突然得到error: assignment of member ‘A::i’ in read-only object....或者尝试设置const int&amp; j。然后你会得到error: assignment of read-only reference ‘j’
  • 这是一个很好的例子,为什么您不应该将原始字段公开为可公开访问的成员。
  • @YurySchkatula 将其设为私有不会改变行为

标签: c++


【解决方案1】:

成员函数上的const 限定符适用于*this 实例。

calc() 中,this 是指向 const A 的指针,但参数 j 是由非常量引用获取的,因此这是完全标准的行为。

现在,如果您尝试在 calc 中分配给 this-&gt;i,代码将无法编译。

void A::calc(const int value) const
{
    i = value; // Compilation error here: i is a data member of a const instance
}

同理,如果set1 是一个const 成员函数,那么代码将无法编译(因为它会尝试将this-&gt;i 绑定到非const 引用获取的参数)

【讨论】:

    【解决方案2】:

    当然。标记方法const 只会使*this const,即该函数承诺不会修改对象通过写入this

    仍然可以通过其他方式修改对象(假设它们也没有标记为const,例如您的示例中的int&amp; j)。

    【讨论】:

    • 其他方式比如投射this
    • @GauravSehgal 不,我的意思是使用引用对象(部分)的其他指针或引用。抛弃 constness 是很顽皮的(如果对象实际上是 const,很快就会导致 UB)。
    【解决方案3】:

    请记住,拥有像const Thing* 这样的“const 指针”或像const Thing&amp; 这样的“const 引用”并不意味着当您拥有指针/引用时,const 限定的对象不能更改。这仅意味着您不能使用该特定指针/引用作为更改它的一种方式。但可能还有其他名称、指针或引用确实允许更改它。

    几个例子:

    void f1(const int& arg1, int& arg2) {
        std::cout << "arg1 before: " << arg1 << "\n";
        arg2 = 4;
        std::cout << "arg1 after: " << arg1 << "\n"; // same thing?
    }
    

    f1 看起来好像必须始终在“之前”和“之后”行中打印相同的值。但如果有人将相同的 int 对象传递给两个参数,则不会:

    void call_f1() {
        int n = 7;
        f1(n, n); // Prints before 7, after 4!
    }
    

    或者,如果函数调用发生在 const 引用的两次使用之间,那么同样可以以某种方式更改变量:

    void something_else();
    void f2(const int& arg) {
        std::cout << "arg before: " << arg << "\n";
        something_else();
        std::cout << "arg after: " << arg << "\n";
    }
    
    int n = 2;
    void something_else() { n = 8; }
    
    void call_f2() {
        f2(n); // Prints before 2, after 8!
    }
    

    因此,在您的void A::calc(int&amp; j, const int value) const 函数中,this 指针确实是const A* const,这意味着您不能使用this 指针更改A 对象。但是仍然可以通过其他方式来改变它,比如在这里你有一个int&amp; j 对非常量对象的引用。如果碰巧j 引用了*this 的子对象,那么修改j 是修改*this 的子对象的有效方法。这类似于我上面的f1 示例,其中arg1 不能用于更改引用的int,但arg2 可以,如果它们引用相同的int,则表示arg1变了。


    当变量定义时首先使用const 限定符,情况略有不同。如果我们写

    const A a(3);
    

    然后我们得到保证(除了在构造函数和析构函数期间),对象不能以任何方式改变。该语言通常会阻止您意外尝试,例如使用a.set1(),但即使您尝试const_cast 技巧,任何实际更改都将是未定义的行为。

    【讨论】:

      【解决方案4】:

      您的代码没有任何问题。声明一个方法const 仅仅意味着this 是常量。但是,您的方法不会(直接)修改thisthis 的任何成员。考虑这个人为的,虽然是正确的例子:

      struct foo {
          int value;
          void modify_const(foo& f) const { f.value = 5; }
      };
      
      int main() {
          foo f;
          f.value = 3;
          f.modify_const(f);
      }
      

      该方法没有修改this,并且参数被声明为非常量,因此在const f上调用f.modify_const(f);将失败,因为参数被作为非常量传递。

      【讨论】:

      • 您不能在const foo f; 上致电f.modify_const(f)。你的意思是写void modify_const(foo&amp; f) const
      • @aschepler ups 是的,感谢您发现了那个愚蠢的错误,没有那个const 这个例子毫无意义;)
      【解决方案5】:

      只是表明你永远不安全。 const 限定符不保证值永远不会改变。

      试试这样,你可以做非常讨厌的事情:

      #include <iostream>
      
      class A {
          const int i;
          void calc(int& j, const int value) const { j = value; }
      public:
          A(int _x) : i(_x) { }
          void set1() const { calc(*const_cast<int*>(&i), 1); }
          int getI() const { return i; }
      };
      
      int main()
      {
          const A a(3);
          std::cout << a.getI() << std::endl;
          a.set1();
          std::cout << a.getI() << std::endl;
      
          return 0;
      }
      

      【讨论】:

      • 这是UB,你是这个意思吗?
      • @PasserBy 我的意思是const 并不意味着没有办法修改一个值。总有办法的。你不能简单地给某个东西贴上标签 const 并假设它的价值永远不会改变。
      • 在语言的规则范围内,const 对象确实不会改变,改变它的是 UB。我不确定调用这种修改对象的方法是否准确
      • @PasserBy true,但在这种情况下,我不同意这个定义。该标准提供了工具和实施工作。然而,由于它是不需要的,他们将其标记为“未定义的行为”。在这种情况下,我宁愿将 UB 解释为“不需要的业务”。与此同时,这个const_cast 仍然有效,所以忽略它会将我们的头埋在沙子里。几乎总有一种方法可以修改 const 对象。
      猜你喜欢
      • 2012-01-29
      • 1970-01-01
      • 2011-03-29
      • 2014-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多