Как копирование объекта с ссылочным членом влияет на объект, на который он ссылается?
При копировании объекта его ссылочный член не создаёт копию связанного объекта: новая ссылка привязывается к тому же объекту. Поэтому два экземпляра класса начинают обращаться к общему состоянию, а изменение объекта через один экземпляр видно через другой.
Ссылки появились как безопасный механизм псевдонимов объектов без явной работы с указателями. Они удобны для передачи объектов без копирования и для представления обязательной связи с уже существующим объектом.
Ссылочный член позволяет объекту хранить такую связь, но это не владение и не независимое значение. Класс фактически содержит привязку к внешнему объекту, поэтому его копирование отличается от копирования обычных полей.
Если разработчик ожидает независимую копию состояния, ссылочный член создаёт скрытое совместное владение доступом к одним данным. Изменение через одну копию изменяет исходный объект и влияет на поведение всех других экземпляров.
Дополнительно ссылку нельзя переназначить после инициализации. Поэтому автоматически сгенерированный оператор копирующего присваивания для класса со ссылочным членом обычно удаляется: компилятор не может перекинуть ссылку на другой объект.
Копирующий конструктор копирует ссылочный член как ссылку на тот же объект, а не как значение этого объекта. Ссылки не являются самостоятельными объектами, которые можно продублировать или переназначить.
У first и second разные объекты View, но их ссылочные члены связаны с одним Storage. Это отличается от поля типа Storage, при копировании которого создавалась бы независимая копия значения.
Копирующее присваивание View по умолчанию недоступно, поскольку операция должна была бы переназначить ссылку. Пользователь может явно определить оператор присваивания, но тогда он должен выбрать семантику: присваивать значение связанного объекта или выполнять другую осмысленную операцию.
Ссылочный член также требует корректного времени жизни связанного объекта. Если внешний объект уничтожен раньше владельца ссылки, ссылка становится висячей; сама ссылка не продлевает время жизни объекта и не управляет его уничтожением.
Выбор ссылочного члена оправдан, когда объект обязан всегда работать с конкретным внешним объектом, например представление или адаптер. Если требуется независимое копирование состояния, лучше хранить значение; если требуется возможность отсутствия объекта или переназначения связи, обычно подходит указатель либо другая явно описанная модель владения.
В компоненте обработки данных класс BufferView хранит ссылку на внешний буфер. Разработчик помещает такие представления в контейнер и копирует их, ожидая получить отдельные буферы для параллельной обработки. На деле все копии продолжают ссылаться на один исходный буфер, поэтому запись из одного обработчика повреждает данные другого.
Рассматривались варианты:
Выбранное решение — разделить представление и владение: буфер хранится отдельно, а класс явно документируется как невладеющее представление. Для независимой обработки создаются отдельные буферы, а копирование BufferView используется только там, где совместный доступ является намеренным.
Нет. Ссылочный член не владеет связанным объектом и не продлевает его время жизни. Если объект-владелец ссылки переживёт связанный объект, любой доступ через ссылку после уничтожения исходного объекта приводит к неопределённому поведению.
Поэтому ссылочный член безопасен только при гарантии, что связанный объект живёт не меньше объекта, содержащего ссылку. Такая гарантия должна обеспечиваться архитектурой программы, а не самим типом ссылки.
В обоих случаях копирование обычно сохраняет связь с тем же внешним объектом, но семантика различается. Ссылка не может быть пустой и не может быть переназначена, тогда как указатель может быть нулевым и изменён после создания объекта.
Для указателя автоматически сгенерированный оператор присваивания обычно копирует адрес. Для ссылки такой оператор по умолчанию удаляется, поскольку ссылка не поддерживает переназначение. Указатель также проще сделать владеющим через явную модель, например умный указатель, тогда как обычная ссылка владение не выражает.
Да, пользователь может определить такой оператор, но он не сможет переназначить ссылку. Обычно оператор присваивает значение объекта, на который указывает ссылка, например копирует содержимое одного связанного объекта в другой.
Это означает, что присваивание меняет внешнее состояние, а не связь экземпляра с объектом. Такая семантика может быть неожиданной, поэтому её следует явно документировать и применять только тогда, когда именно присваивание значения связанного объекта является требуемым поведением.