вспомогательный класс auto_ptr_ref

172
18 декабря 2017, 14:49

Для чего нужен был вспомогательный класс auto_ptr_ref. Нужны примеры простейшей реализации и использования.

Answer 1

auto_ptr_ref - пример попытки "красивой" реализации семантики перемещения (move semantics) средствами языка С++98. Стандартный std::auto_ptr, как известно, должен перемещать право владения памятью при копировании (как конструктором копирования, так и оператором присваивания)

std::auto_ptr<int> pa(new int(42)); // `pa` владеет памятью
std::auto_ptr<int> pb = pa;         // теперь `pb` владеет памятью, `pa` пуст
pa = pb;                            // теперь `pa` владеет памятью, `pb` пуст

Но это создает определенные трудности при реализации всех форм копирования для std::auto_ptr. Так как копирование модифицирует свою правую часть, параметры конструктора копирования и оператора присваивания по идее должны быть неконстантными ссылками на std::auto_ptr. Но в этом случае мы не сможем делать что-то вроде

std::auto_ptr<int> foo() { ... }
...
std::auto_ptr<int> p = foo();

т.к. функция foo() возвращает временный объект, а неконстантные ссылки нельзя привязывать ко временным объектам. Что делать?

Рассмотрим проблему на отвлеченном примере. Предположим, что мы захотели реализовать некий класс S, хранящий некое целочисленное значение и перемещающий это значение в получателя (в левую часть) при копировании. Правая часть при копировании получает значение -1

struct S 
{
  int i;
  S(int i = -1) : i(i) 
    {}
  S(S &s) : i(s.i)
    { s.i = -1; }
  S &operator =(S &s)
  {
    i = s.i;
    s.i = -1;
    return *this;
  } 
};

У нас получилось то, что надо

S a(42);
assert(a.i == 42);
S b = a;
assert(b.i == 42 && a.i == -1);
a = b;
assert(a.i == 42 && b.i == -1);

Однако мы не можем пользоваться нашим классом вот так

S foo() { return S(42); }
...
S a = foo(); // ERROR
S b;
b = foo(); // ERROR

по причине того, что неконстантную ссылку (параметр конструктора и оператора присваивания) невозможно привязать ко временному объекту (к результату foo()).

В такой ситуации мы можем попробовать "выкрутиться" за счет введения промежуточного класса, который мы назовем Sref, и который умеет только ссылаться на наш класс S

struct S;
struct Sref 
{
  S &s;
};

Добавим в наш класс S оператор конверсии к Sref, порождающий Sref-ссылку на самого себя

struct S
{
  ...
  operator Sref() 
  { 
    Sref r = { *this }; 
    return r; 
  } 
  ...
};

А также добавим в наш класс S конвертирующий конструктор из типа Sref и аналогичный конвертирующий оператор присваивания, которые ведут себя в соответствии с требуемой нам семантикой, но через ссылку Sref

struct S
{
  ...
  S(Sref sr) : i(sr.s.i)
    { sr.s.i = -1; }
  S& operator =(Sref sr)
  {
    i = sr.s.i;
    sr.s.i = -1;
    return *this;
  }
  ...
};

и готово - теперь инициализация и присваивание

S a = foo();
S b;
b = foo();

будет прекрасно компилироваться и работать именно так, как мы хотим. Они пойдут по пути

S a(foo().operator Sref());
S b;
b = foo().operator Sref();

Здесь также эксплуатируется то "странноватое" свойство языка, согласно которому хоть к временным объектам и нельзя привязывать неконстантные ссылки, через временные объекты однако можно вызывать их неконстантные методы.

Именно так работает и именно эту роль играет класс auto_ptr_ref в реализации функциональности std::auto_ptr.

Answer 2

У auto_ptr конструктор копирования и оператор присваивания принимают ссылку не на константу, поэтому не смогли бы конструироваться из временного объекта.

Код с собственным классом, демонстрирующий проблему:

struct pointer
{
    pointer() {}
    pointer(pointer &) {}
};

pointer create()
{
    return pointer();
}

int main()
{
    pointer ptr(create());//Ошибка компиляции
}

Добавим свой pointer_ref для решения проблемы:

struct pointer_ref
{
    pointer_ref(const struct pointer&) {}
};

struct pointer
{
    pointer() {}
    pointer(pointer &) {}
    pointer(pointer_ref) {}
};
int main()
{
    pointer ptr(create());//Теперь ok
}

Как реализация будет делать auto_ptr_ref - не важно, главное, чтобы он решал проблему.

READ ALSO
Реализация алгоритма Полига — Хеллмана

Реализация алгоритма Полига — Хеллмана

Алгоритм Полига — Хеллмана

353
Проблема с перезагрузкой оператора

Проблема с перезагрузкой оператора

Пишу очередь с приоритетомНеобходимо перегрузить оператор +, но наталкиваюсь на ошибку: "Вызвано исключение: нарушение доступа для чтения

214