如何更改C ++参考所引用的变量?


Answers:



82

在C ++ 11中,有一个新的(ish)std :: reference_wrapper

#include <functional>

int main() {
  int a = 2;
  int b = 4;
  auto ref = std::ref(a);
  //std::reference_wrapper<int> ref = std::ref(a); <- Or with the type specified
  ref = std::ref(b);
}

这对于在容器中存储引用也很有用。


20

您不能重新分配参考,但是如果您正在寻找可以提供与此功能类似的功能,则可以改用一个指针。

int a = 2;
int b = 4;
int* ptr = &a;  //ptr points to memory location of a.
ptr = &b;       //ptr points to memory location of b now.

您可以使用以下方法获取或设置指针中的值: 

*ptr = 5;     //set
int c = *ptr; //get

4
您要重新声明ptr
Beta Carotin


8

从形式上来讲,这是不可能的,因为它是设计所禁止的。随意地说,这是可能的。

引用存储为指针,因此只要知道如何获取其地址,就可以随时更改其指向的位置。同样,您也可以在无权访问时更改const变量,const成员变量甚至私有成员变量的值。

例如,以下代码更改了类A的const私有成员引用:

#include <iostream>
using namespace std;

class A{
private:
    const int &i1;
public:
    A(int &a):i1(a){}
    int geti(){return i1;}
    int *getip(){return (int*)&i1;}
};

int main(int argc, char *argv[]){
    int i=5, j=10;
    A a(i);
    cout << "before change:" << endl;
    cout << "&a.i1=" << a.getip() << " &i=" << &i << " &j="<< &j << endl;
    cout << "i=" << i << " j=" <<j<< " a.i1=" << a.geti() << endl;
    i=6; cout << "setting i to 6" << endl;
    cout << "i=" << i << " j=" <<j<< " a.i1=" << a.geti() << endl;

    *(int**)&a = &j; // the key step that changes A's member reference

    cout << endl << "after change:" << endl;
    cout << "&a.i1=" << a.getip() << " &i=" << &i << " &j="<< &j << endl;
    cout << "i=" << i << " j=" <<j<< " a.i1=" << a.geti() << endl;
    j=11; cout << "setting j to 11" << endl;
    cout << "i=" << i << " j=" <<j<< " a.i1=" << a.geti() << endl;
    return  0;
}

程序输出:

before change:
&a.i1=0x7fff1b624140 &i=0x7fff1b624140 &j=0x7fff1b624150
i=5 j=10 a.i1=5
setting i to 6
i=6 j=10 a.i1=6

after change:
&a.i1=0x7fff1b624150 &i=0x7fff1b624140 &j=0x7fff1b624150
i=6 j=10 a.i1=10
setting j to 11
i=6 j=11 a.i1=11

如您所见,a.i1最初指向i,更改后它指向j

但是,这样做被认为是危险的,因此不建议这样做,因为这样做违反了数据封装和OOP的原始目的。它更像是内存地址黑客。


那是不正确的:引用根本不需要是指针/对象。根据标准,对于同一对象,它只必须表现为不同的标签。尝试更改未定义的行为。如果您的特定程序中的编译器决定通过指针来实现它,则可以更改它,但这也可能随时中断。
ABaumstumpf

这就是为什么我在第一句话中提到:“从形式上讲,这是不可能的,因为它是设计所禁止的。” 我只是提供了实现级别的解决方法。
xuancong84 '20

5

这不是您想要的方式。C ++只是不允许您重新绑定参考指向的内容。

但是,如果您要使用欺骗手段,则几乎可以使用新的范围对其进行模拟(从不在实际程序中执行此操作):

int a = 2;
int b = 4;
int &ref = a;

{
    int& ref = b; // Shadows the original ref so everything inside this { } refers to `ref` as `b` now.
}

3

使用新的放置位置,可以使参考包装非常容易:

template< class T >
class RefWrapper
{
public:
    RefWrapper( T& v ) : m_v( v ){}

    operator T&(){ return m_v; }
    T& operator=( const T& a ){ m_v = a; return m_v;}
    //...... //
    void remap( T& v )
    {
        //re-map  reference
        new (this) RefWrapper(v);
    }

private:
    T& m_v;
};


 int32 a = 0;
 int32 b = 0;
 RefWrapper< int > r( a );

 r = 1; // a = 1 now
 r.remap( b );
 r = 2; // b = 2 now

3
这个答案比必要的要复杂得多-这个问题非常简单,可以单线回答。
lukelazarovic 2014年

如果我没记错的话,这是C ++ 20之前的格式错误,因为缺少std::launder。您可以只使用一个指针。
HolyBlackCat

3

这个有可能。因为在幕后,引用是指针。以下代码将打印“ hello world”

#include "stdlib.h"
#include "stdio.h"
#include <string>

using namespace std;

class ReferenceChange
{
public:
    size_t otherVariable;
    string& ref;

    ReferenceChange() : ref(*((string*)NULL)) {}

    void setRef(string& str) {
        *(&this->otherVariable + 1) = (size_t)&str;
    }
};

void main()
{
    string a("hello");
    string b("world");

    ReferenceChange rc;

    rc.setRef(a);
    printf("%s ", rc.ref.c_str());

    rc.setRef(b);
    printf("%s\n", rc.ref.c_str());
}

无论您对UB产生什么影响,并不意味着它“有效”
Slava 2015年

@Slava:实际上是未定义的行为吗?可以这么说,引用不是被定义为“变相的指针”吗?
einpoklum

otherVariable使用不需要*((uintptr_t*)this) = ((uintptr_t)&str)setRef
乔阿斯

0

正如其他答案所言,这是不可能的。

但是,如果您将引用存储在class或中struct,则可以使用new放置重新创建整个内容,因此该引用将被重新绑定。正如@HolyBlackCat指出的那样,不要忘记使用std::launder来访问重新创建的对象或使用从new放置位置返回的指针。考虑我的例子:

#include <iostream>

struct A {
    A(int& ref) : ref(ref) {}
    // A reference stored as a field
    int& ref;    
};

int main() {
  int a = 42;
  int b = 43;

  // When instance is created, the reference is bound to a
  A ref_container(a);
  std::cout << 
    "&ref_container.ref = " << &ref_container.ref << std::endl <<
    "&a = " << &a << std::endl << std::endl;

  // Re-create the instance, and bind the reference to b
  A* new_ref_container = new(&ref_container) A(b);
  std::cout <<
    // &ref_container and new_ref_container are the same pointers
    "&ref_container = " << &ref_container << std::endl <<
    "new_ref_container = " << new_ref_container << std::endl <<
    "&new_ref_container.ref = " << &new_ref_container->ref << std::endl <<
    "&b = " << &b << std::endl << std::endl;

  return 0;
}

demo

输出为:

&ref_container.ref = 0x7ffdcb5f8c44
&a = 0x7ffdcb5f8c44

&ref_container = 0x7ffdcb5f8c38
new_ref_container = 0x7ffdcb5f8c38
&new_ref_container.ref = 0x7ffdcb5f8c40
&b = 0x7ffdcb5f8c40

2
如果我没记错的话,这会导致UB C ++ 20之前的版本,因为您忘记了std::launder
HolyBlackCat

@HolyBlackCat是的,我认为您是对的。相关部分是basic.life。我无法使用旧的指针/变量访问新对象。
ivaigult

-5

尽管这是一个坏主意,因为它违背了使用引用的目的,但可以直接更改引用

const_cast< int& >(ref)=b;

9
人们如何为错误的解决方案+1很有趣。这段代码将更改ref指向的变量,而不是引用自身
Slava 2015年

在Exchange网站上,我再也不会感到惊讶了:D
CoffeDeveloper15年

1
嘿,这显然是错误的答案。@visu,请删除它。
einpoklum
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.