const 方法使用引用修改对象

Posted

技术标签:

【中文标题】const 方法使用引用修改对象【英文标题】:const method modifies object using reference 【发布时间】:2018-06-13 11:31:52 【问题描述】:

以下代码调用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 将其设为私有不会改变行为 【参考方案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 对象。

以上是关于const 方法使用引用修改对象的主要内容,如果未能解决你的问题,请参考以下文章

为啥 const 方法不能返回非常量引用?

通过 const 引用传递对象?

将const引用/指针传递给类进行存储的首选方法,而不是复制引用的对象

const限定符

C++,成员函数返回对包含指向 const 对象的指针的向量的 const 引用

向量返回 const 引用,不可能向下转换