C++ Const方法,在不使用Const_cast的情况下修改*此

C++ Const方法,在不使用Const_cast的情况下修改*此,c++,constants,mutable,const-correctness,C++,Constants,Mutable,Const Correctness,以下模式出现在我正在编写的程序中。我希望它不是太做作,但它成功地在const方法Foo::problem()const中变异了Foo对象,而不使用任何const\u cast或类似方法。基本上,Foo存储对FooOwner的引用,反之亦然,在problem()中,Foo通过对其所有者调用mutate\u Foo()在const方法中修改自身。问题遵循代码 #include "stdafx.h" #include <iostream> using namespace std; cla

以下模式出现在我正在编写的程序中。我希望它不是太做作,但它成功地在const方法
Foo::problem()const
中变异了
Foo
对象,而不使用任何const\u cast或类似方法。基本上,
Foo
存储对
FooOwner
的引用,反之亦然,在
problem()
中,
Foo
通过对其所有者调用
mutate\u Foo()
在const方法中修改自身。问题遵循代码

#include "stdafx.h"
#include <iostream>
using namespace std;

class FooOwner;

class Foo {
    FooOwner& owner;
    int data;

public:
    Foo(FooOwner& owner_, int data_)
        : owner(owner_),
          data(data_)
    {
    }

    void SetData(int data_)
    {
        data = data_;
    }

    int Questionable() const;       // defined after FooOwner
};

class FooOwner {
    Foo* pFoo;

public:
    FooOwner()
        : pFoo(NULL)
    {}

    void own(Foo& foo)
    {
        pFoo = &foo;
    }

    void mutate_foo()
    {
        if (pFoo != NULL)
            pFoo->SetData(0);
    }
};

int Foo::Questionable() const
{
    owner.mutate_foo();     // point of interest
    return data;
}

int main()
{
    FooOwner foo_owner;
    Foo foo(foo_owner, 0);      // foo keeps reference to foo_owner
    foo_owner.own(foo);         // foo_owner keeps pointer to foo

    cout << foo.Questionable() << endl;  // correct?

    return 0;
}

您已达到循环依赖。请参阅和yes,即使绕过编译器,修改
const
数据也是无效的。此外,如果不遵守承诺,则会严重削弱编译器的优化能力(请阅读:违犯
const

如果您知道您将通过呼叫其所有者来修改它,为什么
有问题?为什么拥有的对象需要了解所有者?如果您真的需要这样做,那么
mutable
就是最好的选择。这就是它的用途——逻辑常量(与严格的位级常量相反)

从我的n3090草案副本中:

9.3.2此指针的

1在非静态(9.3)成员函数体中,关键字this是一个右值prvalue表达式,其 值是为其调用函数的对象的地址。成员函数中的类型 类X的值是X*如果成员函数声明为const,则其类型为const X*,如果成员 函数被声明为volatile,其类型为volatile X*,如果成员函数被声明 const volatile,其类型为const volatile X*

2在常量成员函数中,调用该函数的对象通过常量访问进行访问 路径因此,常量成员函数不应修改对象及其非静态数据成员

[注意我的]

关于UB:

7.1.6.1简历限定词

3指向cv限定类型的指针或引用实际上不需要 指出或参考合格简历 对象,但它被视为 做常数限定访问路径 无法用于修改对象 即使引用的对象是 非常量对象,可以修改 通过其他访问路径。[ 注意:cv限定符受 类型系统,因此它们不能 未经铸造而被颠覆(5.2.11)。 -[完注]

4除了任何类别 声明为可变的成员(7.1.1)可以 已修改,任何试图修改 常量对象在其生命周期内(3.8) 导致未定义的行为


const
关键字仅在编译时检查时考虑。C++没有提供任何工具来保护类,不需要任何内存访问,这就是你正在使用的指针/引用。编译器和运行时都无法知道指针是否指向您在某处声明了const的实例

编辑:

简短示例(可能无法编译):

//假设foo有一个成员const int foo::datalength()const{…}
//和只读访问方法const char data(int idx)const{…}
for(int i;istd::coutIMO,您并没有做任何技术上的错误。如果成员是指针,可能更容易理解

class X
{
    Y* m_ptr;
    void foo() const {
        m_ptr = NULL; //illegal
        *m_ptr = 42; //legal
    }
};
const
使指针常量,而不是指针对象

考虑以下两者之间的区别:

const X* ptr;
X* const ptr;  //this is what happens in const member functions
至于引用,由于它们无论如何都无法重置,因此方法上的
const
关键字对引用成员没有任何影响


在你的例子中,我没有看到任何const对象,所以你没有做任何坏事,只是在C++中使用一个奇怪的漏洞来验证正确性。

< P>考虑以下内容:

int i = 3;
i
是一个对象,它的类型为
int
。它不是cv限定的(不是
const
volatile
,或两者兼而有之。)

现在我们加上:

const int& j = i;
const int* k = &i;
j
是指
i
的引用,
k
是指
i
的指针(从现在开始,我们简单地将“reference-to”和“points-to”组合为“points-to”。)

此时,我们有两个cv限定变量,
j
k
,它们指向非cv限定对象。这在§7.1中提到。​5.1/3:

指向cv限定类型的指针或引用不需要实际指向或引用cv限定对象,但会被视为指向或引用cv限定对象;即使引用的对象是非常量对象,并且可以通过其他访问路径进行修改,常量限定访问路径也不能用于修改对象。[注:cv限定符受类型系统支持,因此不使用强制转换(5.2.11)就不能被破坏。]

这意味着编译器必须尊重
j
k
是cv限定对象,即使它们指向非cv限定对象。(因此
j=5
*k=5
是非法的,即使
i=5
是合法的。)

我们考虑从<:/P>中删除<代码> const <代码>

const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;

这是非常合法的。我们现在认为<代码> i>代码>是:

const int i = 3;
现在我们的代码是什么

const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
请注意,只需执行以下操作:

const_cast<int&>(j);
*const_cast<int*>(k);
bar
上的
const
对成员有什么作用?它使对成员的访问通过一种称为cv限定访问路径的方式进行。(它通过将
this
的类型从
T*const
更改为
cv T const*
,其中
cv
是函数上的cv限定符。)

那么在代码执行期间,成员类型是什么呢
const int i = 3;
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
int& j = i; // removed const with const_cast...
int* k = &i; // ...but this is not legal!

j = 5;
*k = 5;
const_cast<int&>(j);
*const_cast<int*>(k);
struct foo
{
    foo() :
    me(this), self(*this), i(3)
    {}

    void bar() const
    {
        me->i = 5;
        self.i = 5;
    }

    foo* me;
    foo& self;
    int i;
};
// const-pointer-to-non-const, where the pointer points cannot be changed
foo* const me;

// foo& const is ill-formed, cv-qualifiers do nothing to reference types
foo& self; 

// same as const int
int const i; 
int main()
{
    foo f;
    f.bar(); // UB?
}
const foo f;
f.bar(); // UB!
struct foo
{
    const int& bar() const
    {
        int* result = /* complicated process to get the resulting int */
        return *result; 
    }

    int& bar()
    {
        // we wouldn't like to copy-paste a complicated process, what can we do?
    }

};
int& bar(void)
{
    const foo& self = *this; // add const
    const int& result = self.bar(); // call const version
    return const_cast<int&>(result); // take off const
}
int& bar(void)
{
    return const_cast<int&>( // (3) remove const from result
            static_cast<const foo&>(*this) // (1) add const to this
            .bar() // (2) call const version
            ); 
}
struct foo
{
    foo(void) :
    i(),
    self(*this), me(this),
    self_2(*this), me_2(this)
    {}

    const int& bar() const
    {
        return i; // always well-formed, always defined
    }

    int& bar() const
    {
        // always well-formed, always well-defined
        return const_cast<int&>(
                static_cast<const foo&>(*this).
                bar()
                );
    }

    void baz() const
    {
        // always ill-formed, i is a const int in baz
        i = 5; 

        // always ill-formed, me is a foo* const in baz
        me = 0;

        // always ill-formed, me_2 is a const foo* const in baz
        me_2 = 0; 

        // always well-formed, defined if the foo pointed to is non-const
        self.i = 5;
        me->i = 5; 

        // always ill-formed, type points to a const (though the object it 
        // points to may or may not necessarily be const-qualified)
        self_2.i = 5; 
        me_2->i = 5; 

        // always well-formed, always defined, nothing being modified
        // (note: if the result/member was not an int and was a user-defined 
        // type, if it had its copy-constructor and/or operator= parameter 
        // as T& instead of const T&, like auto_ptr for example, this would 
        // be defined if the foo self_2/me_2 points to was non-const
        int r = const_cast<foo&>(self_2).i;
        r = const_cast<foo* const>(me_2)->i;

        // always well-formed, always defined, nothing being modified.
        // (same idea behind the non-const bar, only const qualifications
        // are being changed, not any objects.)
        const_cast<foo&>(self_2);
        const_cast<foo* const>(me_2);

        // always well-formed, defined if the foo pointed to is non-const
        // (note, equivalent to using self and me)
        const_cast<foo&>(self_2).i = 5;
        const_cast<foo* const>(me_2)->i = 5;

        // always well-formed, defined if the foo pointed to is non-const
        const_cast<foo&>(*this).i = 5;
        const_cast<foo* const>(this)->i = 5;
    }

    int i;

    foo& self;
    foo* me;
    const foo& self_2;
    const foo* me_2;
};

int main()
{
    int i = 0;
    {
        // always well-formed, always defined
        int& x = i;
        int* y = &i;
        const int& z = i;
        const int* w = &i;

        // always well-formed, always defined
        // (note, same as using x and y)
        const_cast<int&>(z) = 5;
        const_cast<int*>(w) = 5;
    }

    const int j = 0;
    {
        // never well-formed, strips cv-qualifications without a cast
        int& x = j;
        int* y = &j;

        // always well-formed, always defined
        const int& z = i;
        const int* w = &i;

        // always well-formed, never defined
        // (note, same as using x and y, but those were ill-formed)
        const_cast<int&>(z) = 5;
        const_cast<int*>(w) = 5;
    }

    foo x;
    x.bar(); // calls non-const, well-formed, always defined
    x.bar() = 5; // calls non-const, which calls const, removes const from
                 // result, and modifies which is defined because the object
                 // pointed to by the returned reference is non-const,
                 // because x is non-const.

    x.baz(); // well-formed, always defined

    const foo y;
    y.bar(); // calls const, well-formed, always defined
    const_cast<foo&>(y).bar(); // calls non-const, well-formed, 
                               // always defined (nothing being modified)
    const_cast<foo&>(y).bar() = 5; // calls non-const, which calls const,
                                   // removes const from result, and
                                   // modifies which is undefined because 
                                   // the object pointed to by the returned
                                   // reference is const, because y is const.

    y.baz(); // well-formed, always undefined
}
class ComplexProcessor
{
public:
   void setInputs( int a, int b );
   int getValue() const;
private:
   int complexCalculation( int a, int b );
   int result;
};
void ComplexProcessor::setInputs( int a, int b ) {
   result = complexCalculation( a, b );
}
class ComplexProcessor2 {
public:
   void setInputs( int a, int b ) {
      a_ = a; b_ = b;
   }
   int getValue() const {
      return complexCalculation( a_, b_ );
   }
private:
   int complexCalculation( int a, int b );
   int a_,b_;
};
class ComplexProcessor3 {
public:
   ComplexProcessor3() : cached_(false) {}
   void setInputs( int a, int b ) {
      a_ = a; b_ = b;
      cached_ = false;
   }
   int getValue() const {
      if ( !cached_ ) {
         result_ = complexCalculation( a_, b_ );
         cached_ = true;
      }
      return result_;
   }
private:
   int complexCalculation( int a, int b );
   int a_,b_;
   // This are not part of the perceivable state:
   mutable int result_;
   mutable bool cached_;
};
template <typename T>
class SharedValue {
public:
   void set( T v ) {
      scoped_lock lock(mutex_);
      value = v;
   }
   T get() const {
      scoped_lock lock(mutex_);
      return value;
   }
private:
   T value;
   mutable mutex mutex_;
};