Как я могу изменить переменную, на которую ссылается 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;
}
Результат:
&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;