Как я могу изменить переменную, на которую ссылается C++?

Если у меня есть это:

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

Как я могу сделать ref Ссылаться на b после этого кода?

9 ответов

Решение

Это невозможно, и это по замыслу. Ссылки не могут быть отскок.

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

Это также полезно для хранения ссылок в контейнерах.

Вы не можете переназначить ссылку, но если вы ищете что-то, что могло бы предоставить аналогичные возможности, вы можете вместо этого сделать указатель.

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

Формально говоря, это невозможно, так как это запрещено дизайном. Произвольно говоря, это возможно.

Ссылки хранятся в виде указателя, поэтому вы всегда можете изменить место, на которое оно указывает, до тех пор, пока вы знаете, как получить его адрес. Точно так же вы можете изменить значение константных переменных, константных переменных-членов или даже закрытых переменных-членов, когда у вас нет доступа к ним.

Например, следующий код изменил константную частную ссылку на член класса A:

#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.

Тем не менее, это считается опасным и, следовательно, не рекомендуется, потому что это противоречит первоначальной цели инкапсуляции данных и ООП. Это больше похоже на взлом адреса памяти.

Вы не можете переназначить ссылку.

Это не возможно так, как вы хотите. 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.
}

Вы можете сделать эталонную оболочку очень просто, используя новое размещение:

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

Это возможно. Потому что под капотом ссылка является указателем. Следующий код напечатает "Привет, мир"

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

Это невозможно, как утверждают другие ответы.

Однако, если вы сохраните ссылку в class или же struct, вы можете воссоздать все это, используя новое размещение, поэтому ссылка будет повторно привязана. Как отметил @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

Хотя это плохая идея, так как она отрицает цель использования ссылок, можно напрямую изменить ссылку

const_cast< int& >(ref)=b;
Другие вопросы по тегам