修改*this而不使用const_cast的Const方法

Ash*_*ain 9 c++ const const-correctness mutable

在我正在编写的程序中出现了以下模式.我希望它不是太做作,但它设法Foo在const方法中改变一个对象Foo::Questionable() const,而不使用任何const_cast或类似的东西.基本上,Foo存储参考FooOwner,反之亦然,并在Questionable(),Foo设法修改本身在一个const方法通过调用mutate_foo()关于它的主人.问题遵循代码.

#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;
}
Run Code Online (Sandbox Code Playgroud)

这是定义的行为吗?应该Foo::data声明可变吗?或者这是一个标志我做的事情是致命的错误?我正在尝试实现一种懒惰初始化的'数据',它只在被请求时设置,并且下面的代码编译好没有警告,所以我有点紧张我在UB土地.

编辑:conston Questionable()只使直接成员为const,而不是对象指向或引用的对象.这会使代码合法吗?我对以下事实感到困惑:in Questionable(),thishas the type const Foo*,以及调用堆栈的下游,FooOwner合法地有一个用于修改的非const指针Foo.这是否意味着Foo可以修改对象?

编辑2:也许是一个更简单的例子:

class X {
    X* nonconst_this;   // Only turns in to X* const in a const method!
    int data;

public:
    X()
        : nonconst_this(this),
          data(0)
    {
    }

    int GetData() const
    {
        nonconst_this->data = 5;    // legal??
        return data;
    }
};
Run Code Online (Sandbox Code Playgroud)

GMa*_*ckG 25

考虑以下:

int i = 3;
Run Code Online (Sandbox Code Playgroud)

i是一个对象,它有类型int.它不是CV-合格的(不是constvolatile,或两者兼而有之.)

现在我们添加:

const int& j = i;
const int* k = &i;
Run Code Online (Sandbox Code Playgroud)

j是指向的参考i,并且k是指向的指针i.(从现在开始,我们只需将"引用"和"指向"组合成"指向".)

此时,我们有两个cv限定的变量,jk指向一个非cv限定的对象.这在§7.1.5.1/ 3中提到:

对cv限定类型的指针或引用不需要实际指向或引用cv限定对象,但它被视为具有; 即使引用的对象是非const对象并且可以通过某些其他访问路径进行修改,也不能使用const限定的访问路径来修改对象.[注意:类型系统支持cv-qualifiers,因此在不进行强制转换的情况下不能破坏它们(5.2.11).]

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

我们现在考虑删除const那些:

const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
Run Code Online (Sandbox Code Playgroud)

这是合法的(§参见5.2.11),但它是不确定的行为?见§7.15.1/4:

除了可以修改声明为mutable(7.1.1)的任何类成员之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为. 强调我的.

请记住,i不是 constjk两个点i.我们所做的就是告诉类型系统从类型中删除const限定符,这样我们就可以修改指向的对象,然后i通过这些变量进行修改.

这跟做的完全一样:

int& j = i; // removed const with const_cast...
int* k = &i; // ..trivially legal code

j = 5;
*k = 5;
Run Code Online (Sandbox Code Playgroud)

这是非常合法的.我们现在考虑的i是:

const int i = 3;
Run Code Online (Sandbox Code Playgroud)

我们现在的代码是什么?

const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
Run Code Online (Sandbox Code Playgroud)

它现在导致未定义的行为,因为它i是一个const限定的对象.我们告诉类型系统删除const所以我们可以修改指向的对象,然后修改一个const限定的对象.如上所述,这是未定义的.

再次,更明显的是:

int& j = i; // removed const with const_cast...
int* k = &i; // ...but this is not legal!

j = 5;
*k = 5;
Run Code Online (Sandbox Code Playgroud)

请注意,只需这样做:

const_cast<int&>(j);
*const_cast<int*>(k);
Run Code Online (Sandbox Code Playgroud)

完全合法和定义,因为没有修改const限定对象; 我们只是搞乱了类型系统.


现在考虑:

struct foo
{
    foo() :
    me(this), self(*this), i(3)
    {}

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

    foo* me;
    foo& self;
    int i;
};
Run Code Online (Sandbox Code Playgroud)

是什么constbar做的成员?它使得访问它们通过称为cv限定访问路径的东西.(它通过更改thisfrom 的类型来T* const实现cv T const*,其中cv是函数上的cv限定符.)

那么执行期间的成员类型是bar什么?他们是:

// 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; 
Run Code Online (Sandbox Code Playgroud)

当然,类型是无关紧要的,因为重要的是指向对象的const限定,而不是指针.(k如上所述const int* const,后者const无关紧要.)我们现在考虑:

int main()
{
    foo f;
    f.bar(); // UB?
}
Run Code Online (Sandbox Code Playgroud)

bar,双方meself指向一个非const foo,所以只是想int i上面我们已经明确的行为.我们有没有:

const foo f;
f.bar(); // UB!
Run Code Online (Sandbox Code Playgroud)

我们本来就有UB,const int因为我们将修改一个const限定对象.

在您的问题中,您没有const限定对象,因此您没有未定义的行为.


只是为了增加对权威的吸引力,考虑const_castScott Meyers 的技巧,用于在非const函数中回收const限定函数:

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?
    }

};
Run Code Online (Sandbox Code Playgroud)

他建议:

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
}
Run Code Online (Sandbox Code Playgroud)

或者通常如何写:

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
            ); 
}
Run Code Online (Sandbox Code Playgroud)

请注意,这又是完全合法且定义明确的.具体来说,因为必须在非const限定的情况下调用此函数,所以foo我们完全可以从返回类型中剥离const限定int& boo() const.

(除非有人const_cast首先用+呼叫射击自己.)


总结一下:

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
}
Run Code Online (Sandbox Code Playgroud)

我指的是ISO C++ 03标准.

  • 可能是我读过很长时间的最佳答案.清楚,很好解释并准确回答我的想法.谢谢,希望你得到你应得的代表:) (2认同)

Unc*_*ens 6

IMO,你没有做任何技术错误.可能会更容易理解成员是否是指针.

class X
{
    Y* m_ptr;
    void foo() const {
        m_ptr = NULL; //illegal
        *m_ptr = 42; //legal
    }
};
Run Code Online (Sandbox Code Playgroud)

const使指针成为 const,而不是指针.

考虑以下区别:

const X* ptr;
X* const ptr;  //this is what happens in const member functions
Run Code Online (Sandbox Code Playgroud)

至于引用,因为它们无论如何都不能重新设置,const方法上的关键字对引用成员没有任何影响.

在你的例子中,我没有看到任何const对象,所以你没有做任何坏事,只是利用const正确性在C++中工作的方式的一个奇怪的漏洞.