const方法修改*这没有const_cast [英] Const method that modifies *this without const_cast

查看:129
本文介绍了const方法修改*这没有const_cast的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我写的程序中出现了以下模式。我希望它不是太有愧疚,但它设法在const方法中改变一个 Foo 对象 Foo :: Questionable()const ,不使用任何const_cast或类似的。基本上, Foo 存储对 FooOwner 的引用,反之亦然, Questionable / code>, Foo 通过在其所有者上调用 mutate_foo() 。问题遵循代码。

  #includestdafx.h
#include< iostream>
using namespace std;

class FooOwner;

class Foo {
FooOwner&所有者;
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(); //兴趣点
返回数据;
}

int main()
{
FooOwner foo_owner
Foo foo(foo_owner,0); // foo保持引用foo_owner
foo_owner.own(foo); // foo_owner保持指针foo

cout<< foo.Questionable()<< endl; //正确?

return 0;
}

这是定义的行为吗?应该 Foo :: data 被声明为mutable吗?或者这是一个迹象,我做的事情致命错了吗?我试图实现一种懒惰初始化的数据,只有在请求时设置,以下代码编译正常,没有警告,所以我有点紧张,我在UB土地。



编辑:Questionable()上的 const 只使立即成员const,而不是对象指向或引用的对象。这是否使代码合法?我在 Questionable()这个类型 const Foo * 合法地具有非const指针,它用于修改 Foo / code>。这是否意味着 Foo 对象可以被修改或不被修改?



编辑2:也许一个更简单的例子: / p>

  class X {
X * nonconst_this; //只在const方法中转换为X * const。
int data;

public:
X()
:nonconst_this(this),
data(0)
{
}
b $ b int GetData()const
{
nonconst_this-> data = 5; // legal?
return data;
}
};


解决方案

请考虑以下方面:

  int i = 3; 

i 是一个对象,类型 int 。它不是cv限定的(不是 const volatile 或两者。)



现在我们添加:

  const int& j = i; 
const int * k =& i;

j code> i k 是指向 i 的指针。 (从现在开始,我们简单地将指向和指向结合为指向。)



此时,我们有两个cv-变量, j k ,指向非cv限定的对象。 5.1 / 3:


指向cv限定类型的指针或引用不需要实际指向或引用到cv限定的对象,但它被视为好像它;即使引用的对象是非const对象并且可以通过其他访问路径进行修改,const限定的访问路径也不能用于修改对象。 [注意:类型系统支持cv限定符,因此它们不能被转换而不转换(5.2.11)。 ]


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



现在我们考虑从中删除 const p>

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

这是合法的(§代表5.2.11),但它是未定义的行为吗? 否。参见§7.1。5.1 / 4:


1)可以修改,任何尝试修改const对象在其生命周期(3.8)导致未定义的行为
强调我。


请记住, i 不是 const 并且 j k 都指向 i 。我们所做的就是告诉类型系统从类型中删除const-qualifier,这样我们可以修改指向的对象,然后通过这些变量修改 i 。 / p>

这与完全一样:

  int& j = i; // removed const with const_cast ... 
int * k =& i; // ..主要法律代码

j = 5;
* k = 5;

这是合法的。我们现在认为 i 是这样的:

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



<$ p


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

现在会导致未定义的行为,因为 i 是一个const限定的对象。我们告诉类型系统删除 const ,以便我们可以修改指向的对象,然后修改一个const限定的对象



再次,更为明显的是:

  int& j = i; // removed const with const_cast ... 
int * k =& i; // ...但这不合法!

j = 5;
* k = 5;

注意,只需这样做:

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

完全合法并定义,因为没有修改const限定的对象;


$ b





$ b $ b

  struct foo 
{
foo():
me(this),self $ b {}

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

foo * me;
foo&自;
int i;
};

const c> bar 做成员?它允许访问他们通过一个称为 cv限定访问路径的东西。 (它通过将类型从 T * const 改为 cv T const * ,其中 cv 是函数上的cv-qualifiers。)



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

  //常量指针到非常量,其中指针点不能更改
foo * const me;

// foo& const是不成形的,cv-qualifiers什么都不引用类型
foo&自;

//与const int相同int
int const i;

当然,类型是不相关的,重要的是 >指向对象,而不是指针。 ( k 上面是 const int * const ,后者 const

  int main()
{
foo f;
f.bar(); // UB?
}

bar me self 指向非常量 foo ,所以就像上面的 int i ,我们有明确定义的行为。我们有:

  const foo f; 
f.bar(); // UB!

我们会有UB,就像 const int ,因为我们将修改一个const限定的对象。



在你的问题中,没有const限定的对象,所以你没有未定义的行为。 / p>




只是为了增加对权威的吸引力,请考虑 const_cast Scott Meyers的窍门,用于在非const函数中循环一个const限定的函数:

  struct foo 
{
const int& bar()const
{
int * result = / *复杂的过程得到的结果int * /
return * result;
}

int& bar()
{
//我们不想复制 - 粘贴一个复杂的过程,我们能做什么?
}

};

他建议:

  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&>(//从结果中删除const
static_cast< const foo& add const to this
.bar()//(2)call const version
);
}

注意,这是完全合法的,具体来说,因为这个函数必须在一个非const限定的 foo 上调用,所以我们可以非常安全地从 int&



(除非有人用 const_cast +调用)






总结:

  struct foo 
{
foo(void):
i(),
self(* this),me b self_2(* this),me_2(this)
{}

const int& bar()const
{
return i; //总是形式良好,总是定义
}

int& bar()const
{
//总是很好的,总是很好定义
return const_cast< int&>(
static_cast< const foo&> )。
bar()
);
}

void baz()const
{
//总是不成形,i是baz中的const int
i = 5;

//总是不成形,我是一个foo * const在baz
me = 0;

//总是不合格,me_2是一个const foo * const in baz
me_2 = 0;

//总是格式良好,如果foo指向的是非const的话,定义
self.i = 5;
me-> i = 5;

//总是不成形,类型指向一个const(虽然对象它
//指向可能或不一定是const限定的)
self_2.i = 5;
me_2-> i = 5;

//总是结构合理,总是定义,没有被修改
//(注意:如果result / member不是一个int并且是用户定义的
/ / type,如果它有它的复制构造函数和/或operator =参数
//作为T&而不是const T&,像auto_ptr,这将定义
//如果foo self_2 / me_2指向非const
int r = const_cast< foo&>(self_2).i;
r = const_cast< foo * const>(me_2) - > i;

//总是格式良好,总是定义,没有被修改
//(在非const bar后面有同样的想法,只有const qualifications
//正在被改变,而不是任何对象。)
const_cast< foo&>(self_2);
const_cast (me_2);

//总是格式合理,是非const
//(注意,等同于使用self和me)
const_cast< foo&>(self_2).i = 5;
const_cast< foo * const>(me_2) - > i = 5;

//总是结构良好,如果foo指向是非const的话就定义
const_cast< foo&>(* this).i = 5;
const_cast< foo * const>(this) - > i = 5;
}

int i;

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

int main()
{
int i = 0;
{
//总是格式合理,总是定义
int& x = i;
int * y =& i;
const int& z = i;
const int * w =& i;

//总是定义良好的,总是定义
//(注意,与使用x和y相同)
const_cast< int&>(z)= 5;
const_cast< int *>(w)= 5;
}

const int j = 0;
{
//没有良好的结构,strip cv-qualifications没有转换
int& x = j;
int * y =& j;

//总是很好的,总是定义
const int& z = i;
const int * w =& i;

//总是结构良好,从未定义
//(注意,与使用x和y相同,但是那些是不成形的)
const_cast< int& (z)= 5;
const_cast< int *>(w)= 5;
}

foo x;
x.bar(); //调用非常量,形式良好,总是定义
x.bar()= 5; //调用非const,它调用const,从
// result中删除const,并修改其定义,因为返回的引用指向的对象
//是非const,
//因为x是非const。

x.baz(); //形式良好,总是定义

const foo y;
y.bar(); //调用const,形式良好,总是定义
const_cast< foo&>(y).bar(); //调用非常量,形式良好,
//总是定义(没有被修改)
const_cast< foo&>(y).bar()= 5; //调用非const,它调用const,
//从结果中删除const,
//修改哪个未定义,因为
//返回的对象指向的对象
// reference是const,因为y是const。

y.baz(); //形式良好,总是未定义
}

C ++ 03标准。


The following pattern has arisen in a program I'm writing. I hope it's not too contrived, but it manages to mutate a Foo object in the const method Foo::Questionable() const, without use of any const_cast or similar. Basically, Foo stores a reference to FooOwner and vice versa, and in Questionable(), Foo manages to modify itself in a const method by calling mutate_foo() on its owner. Questions follow the code.

#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;
}

Is this defined behavior? Should Foo::data be declared mutable? Or is this a sign I'm doing things fatally wrong? I'm trying to implement a kind of lazy-initialised 'data' which is only set when requested, and the following code compiles fine with no warnings, so I'm a little nervous I'm in UB land.

Edit: the const on Questionable() only makes immediate members const, and not the objects pointed to or referenced by the object. Does this make the code legal? I'm confused between the fact that in Questionable(), this has the type const Foo*, and further down the call stack, FooOwner legitimately has a non-const pointer it uses to modify Foo. Does this mean the Foo object can be modified or not?

Edit 2: perhaps an even simpler example:

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

解决方案

Consider the following:

int i = 3;

i is an object, and it has the type int. It is not cv-qualified (is not const or volatile, or both.)

Now we add:

const int& j = i;
const int* k = &i;

j is a reference which refers to i, and k is a pointer which points to i. (From now on, we simply combine "refer to" and "points to" to just "points to".)

At this point, we have two cv-qualified variables, j and k, that point to a non-cv-qualified object. This is mentioned in §7.1.​5.1/3:

A pointer or reference to a cv-qualified type need not actually point or refer to a cv-qualified object, but it is treated as if it does; a const-qualified access path cannot be used to modify an object even if the object referenced is a non-const object and can be modified through some other access path. [Note: cv-qualifiers are supported by the type system so that they cannot be subverted without casting (5.2.11). ]

What this means is that a compiler must respect that j and k are cv-qualified, even though they point to a non-cv-qualified object. (So j = 5 and *k = 5 are illegal, even though i = 5 is legal.)

We now consider removing the const from those:

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

This is legal (§refer to 5.2.11), but is it undefined behavior? No. See §7.1.​5.1/4:

Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior. Emphasis mine.

Remember that i is not const and that j and k both point to i. All we've done is tell the type system to remove the const-qualifier from the type so we can modify the pointed to object, and then modified i through those variables.

This is exactly the same as doing:

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

j = 5;
*k = 5;

And this is trivially legal. We now consider that i was this instead:

const int i = 3;

What of our code now?

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

It now leads to undefined behavior, because i is a const-qualified object. We told the type system to remove const so we can modify the pointed to object, and then modified a const-qualified object. This is undefined, as quoted above.

Again, more apparent as:

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

j = 5;
*k = 5;

Note that simply doing this:

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

Is perfectly legal and defined, as no const-qualified objects are being modified; we're just messing with the type-system.


Now consider:

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

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

    foo* me;
    foo& self;
    int i;
};

What does const on bar do to the members? It makes access to them go through something called a cv-qualified access path. (It does this by changing the type of this from T* const to cv T const*, where cv is the cv-qualifiers on the function.)

So what are the members types during the execution of bar? They are:

// 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; 

Of course, the types are irrelevant, as the important thing is the const-qualification of the pointed to objects, not the pointers. (Had k above been const int* const, the latter const is irrelevant.) We now consider:

int main()
{
    foo f;
    f.bar(); // UB?
}

Within bar, both me and self point to a non-const foo, so just like with int i above we have well-defined behavior. Had we had:

const foo f;
f.bar(); // UB!

We would have had UB, just like with const int, because we would be modifying a const-qualified object.

In your question, you have no const-qualified objects, so you have no undefined behavior.


And just to add an appeal to authority, consider the const_cast trick by Scott Meyers, used to recycle a const-qualified function in a non-const function:

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

};

He suggests:

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
}

Or how it's usually written:

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
            ); 
}

Note this is, again, perfectly legal and well-defined. Specifically, because this function must be called on a non-const-qualified foo, we are perfectly safe in stripping the const-qualification from the return type of int& boo() const.

(Unless someone shoots themselves with a const_cast + call in the first place.)


To summarize:

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
}

I refer to the ISO C++03 standard.

这篇关于const方法修改*这没有const_cast的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆