Есть ли элегантный способ обмена ссылками на С++?

Иногда классы ссылаются на другие классы. Реализация std::swap() для таких классов не может быть простой, поскольку это приведет к замене исходных экземпляров вместо ссылок. Приведенный ниже код иллюстрирует это поведение:

#include <iostream>

class A
{
   int& r_;
public:
   A(int& v) : r_(v) {}
   void swap(A& a)
   {
      std::swap(r_, a.r_);
   }
};

void test()
{
   int x = 10;
   int y = 20;

   A a(x), b(y);
   a.swap(b);

   std::cout << "x=" << x << "\n"
             << "y=" << y << "\n";
}

int main()
{
    test();
    return 0;
}

Простой обходной путь с объединением:

class A
{
   union
   {
      int& r_;
      size_t t_;
   };
public:
   A(int& v) : r_(v) {}
   void swap(A& a)
   {
      std::swap(t_, a.t_);
   }
};

Это эффективный, но не красивый. Есть ли лучший способ заменить две ссылки на С++? Кроме того, как стандарт С++ объясняет смешение ссылок и значений в одном объединении, учитывая, что в книге Stroustrup "Язык программирования С++" "ссылка" определяется как "альтернативное имя объекта, псевдоним" (стр .189).

Ответ 1

Объединенный трюк, который вы используете, примерно так же не переносится, как и код. Стандарт не устанавливает никаких требований относительно того, как компиляторы реализуют ссылки. Они могут (и, скорее всего, делать) использовать указатели под капотом, но это никогда не гарантируется. Не говоря уже о том, что sizeof(size_t) и sizeof(T*) не обязательно должны быть равными.

Лучший ответ на вашу проблему: не используйте ссылочные элементы, если вам нужен назначаемый класс /swappable. Вместо этого используйте вместо этого указатель. В конце концов, ссылки не подлежат переустановке по определению, но, желая, чтобы класс swappable, вы хотите что-то повторно. И это указатель.

Ответ 2

Вы можете использовать std::reference_wrapper вместо прямой ссылки (которой вы не можете swap) или указателя (который может быть nullptr). Что-то вроде:

class A
{
    std::reference_wrapper<int> r;
public:
   A(int& v) : r(v) {}
   void swap(A& rhs)
   {
      std::swap(r, rhs.r);
   }

   int& get() const { return r; }
};

Живой пример

Ответ 3

Ваша программа, использующая union, не является законной, поскольку вы назначаете ссылку, но меняете целое число, а не намереваетесь снова использовать ссылку. Вы можете прочитать о том, почему здесь: Доступ к неактивному члену профсоюза и поведению undefined?

Простым решением здесь является использование указателей вместо ссылок. Тогда все будет работать гладко, без специального кода. Если вам это действительно не нравится, возможно, вы можете использовать boost::optional<int&> вместо простых ссылок, но я бы изо всех сил пытался понять, как это будет лучше.

Ответ 4

По существу, что вы хотели бы сделать, если я понимаю ваш пример, это переподготовка ссылок. Этого нельзя сделать на С++. Вы можете либо обменять значения в упомянутых объектах, либо использовать указатели.