【问题标题】:How can I change the variable to which a C++ reference refers?如何更改 C++ 引用所引用的变量?
【发布时间】:2011-12-04 12:34:24
【问题描述】:

如果我有这个:

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

如何让ref 在这段代码之后引用b

【问题讨论】:

    标签: c++ reference dynamic-rebinding


    【解决方案1】:

    这是不可能的,那就是by design。引用不能被反弹。

    【讨论】:

    • 在下面的代码中,我将 x 的引用更改为 k 并且仍然得到结果。你能告诉我这是怎么可能的吗?诠释a = 50;诠释 &x=a;诠释 k=10; cout
    • 您没有更改引用,而是将k 的值分配给x 引用的对象。在此分配之后,a == kx 仍然指代 a
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      您无法重新分配引用,但如果您正在寻找可以提供与此类似功能的东西,您可以改为使用指针。

      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
      

      【讨论】:

      • 您正在重新声明ptr
      【解决方案4】:

      您不能重新分配参考。

      【讨论】:

        【解决方案5】:

        从形式上讲,这是不可能的,因为它是被设计禁止的。随便说,是可以的。

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

        例如,下面的代码改变了类 A 的 const private 成员引用:

        #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 的最初目的。这更像是内存地址黑客攻击。

        【讨论】:

        • 那不正确:引用根本不需要是指针/对象。根据标准,它只需要表现得像同一个对象的不同标签。试图改变它是未定义的行为。如果你的特定程序中的编译器决定通过指针来实现它,你也许可以改变它,但这也可能随时中断。
        • 这就是为什么我在第一句话中提到:“从形式上讲,这是不可能的,因为它是设计禁止的。”我只是提供了一个实现级别的解决方法。
        【解决方案6】:

        这是不可能的。 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.
        }
        

        【讨论】:

          【解决方案7】:

          您可以使用放置新的位置非常轻松地制作引用包装器:

          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
          

          【讨论】:

          • 这个答案比必要的复杂得多——这个问题很简单,可以用一条线来回答。
          • 如果我没记错的话,这是因为缺少std::launder 而在 C++20 之前的格式不正确。你可以只使用一个指针。
          【解决方案8】:

          这是可能的。因为在引擎盖下,引用是一个指针。 以下代码将打印“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:这实际上是未定义的行为吗?可以这么说,引用是否未定义为“变相的指针”?
          • otherVariable 不需要在*((uintptr_t*)this) = ((uintptr_t)&amp;str) 中使用setRef
          【解决方案9】:

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

          但是,如果您将引用存储在classstruct 中,您可以使用placement new 重新创建整个事物,因此引用被重新绑定。正如@HolyBlackCat 所指出的,不要忘记使用std::launder 访问重新创建的对象或使用从新位置返回的指针。考虑我的例子:

          #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
          

          【讨论】:

          • 如果我没记错的话,这会导致 UB pre-C++20 因为你忘记了std::launder
          • @HolyBlackCat 是的,我认为你是对的。相关部分是basic.life。我无法通过旧指针/变量访问新对象。
          【解决方案10】:

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

          const_cast< int& >(ref)=b;
          

          【讨论】:

          • 有趣的是人们如何 +1 错误的解决方案。此代码将更改 ref 指向的变量,而不是引用自身
          • 在 Exchange 网站上再也没有什么让我感到惊讶了:D
          • 嘿,这显然是一个错误的答案。 @visu,请删除它。
          猜你喜欢
          • 2014-10-07
          • 1970-01-01
          • 1970-01-01
          • 2017-06-12
          • 1970-01-01
          • 2014-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多