Требования std::is_tribuly_copyable

Стандарт С++ (и несколько SO answers) утверждает, что для того, чтобы квалифицироваться как is_trivially_copyable<T>, тип T должен иметь:

  1. Деструктор по умолчанию,
  2. Никаких виртуальных функций,
  3. Никаких виртуальных базовых классов.

(Это не единственные требования, но вопрос сосредоточен только на них)

Кто-нибудь может пролить свет на почему? Я не понимаю, как нарушение любого из этих 3 делает массив T небезопасным для memcpy.


person Ofek Shilon    schedule 15.07.2015    source источник
comment
Подумайте о виртуальном операторе присваивания (хотя и не используемом в нормальных программах).   -  person Mohit Jain    schedule 15.07.2015
comment
Какое отношение is_trivially_copyable имеет к memcpy?   -  person 101010    schedule 15.07.2015
comment
@Mohit Jain: это не единственное требование - отсутствие назначения не по умолчанию - это отдельное требование, как я понимаю.   -  person Ofek Shilon    schedule 15.07.2015
comment
@ 101010: is_tribuly_copyable рекламируется как тест, который необходимо выполнить перед выполнением memcpy.   -  person Ofek Shilon    schedule 15.07.2015
comment
@OfekShilon Я привел аргумент в поддержку одного из требований. Также виртуальная функция и виртуальная база могут включать указатель на объект, что делает сериализацию бессмысленной.   -  person Mohit Jain    schedule 15.07.2015
comment
@Mohit Jain: возможно, я не совсем ясно выразился. Помимо этих трех требований, тип is_tribuly_copyable не должен иметь нетривиальных операторов присваивания копирования (13.5.3, 12.8), как виртуальных, так и нет. Тип с виртуальным оператором присваивания не может квалифицироваться как is_tribuly_copyable независимо от этих 3 условий.   -  person Ofek Shilon    schedule 15.07.2015
comment
@OfekShilon Я знаю требования и понимаю, почему пункты № 2 и № 3, упомянутые вами, важны. И требование №1 также каким-то образом может сводиться к виртуальным деструкторам и, следовательно, к указателям.   -  person Mohit Jain    schedule 15.07.2015
comment
@MohitJain: Я уверен, что да, пожалуйста, не спешите так обижаться. Вопрос касается обоснования требований. И обратите внимание, что этот обсуждаемый трейт является is_tribuly_copyable, а не is_tribuly_serializable.   -  person Ofek Shilon    schedule 15.07.2015
comment
@OfekShilon Извините, если это звучит для вас оскорбительно. Дело в том, что я не знал точного ответа и начал дискуссию, думая, что это будет полезно. В любом случае, неважно.   -  person Mohit Jain    schedule 15.07.2015
comment
@MohitJain: почему memcpy-ing класс с указателем на vtable бессмысленен? Он не является указателем на какой-то внутренний буфер и одинаков для всех одинаковых объектов в одном и том же процессе.   -  person Ofek Shilon    schedule 15.07.2015
comment
N2762 кажется предложением который ввел термин тривиально копируемый.   -  person cpplearner    schedule 15.07.2015


Ответы (1)


Что касается 1 («деструктор по умолчанию»), это просто потому, что memcpy нового объекта в существующую переменную не вызовет деструктор того, что он перезаписывает, поэтому, если класс зависит от чего-либо в этом деструкторе, его ограничения могут быть нарушен.

Что касается 2 («нет виртуальных функций»), вероятно, причина в том, что когда происходит нарезка объекта, нарезанный объект должен правильно функционировать как объект базового класса.

Представьте себе базовый и производный класс таким образом:

class Base {
    int b;
    virtual void f() { ++b; }
}

class Derived : public Base {
    int d;
    void f() override { ++d; }
}

Теперь предположим, что у вас есть Base& переменная v, которая на самом деле ссылается на Derived объект. Если бы std::is_trivially_copyable<Base> было истинным, вы могли бы memcpy из этой переменной в другой Base объект w (это скопировало бы b и vtable). Если бы вы сейчас позвонили w.f(), вы бы позвонили (через виртуальную таблицу) Derived::f(). Что, конечно, было бы неопределенным, поскольку w.d не имеет выделенного хранилища.

Это также может составлять 3 («нет виртуальных базовых классов»), но, поскольку я практически никогда не использую виртуальные базовые классы, я уступлю их кому-то более знакомому с ними.

person Toby Speight    schedule 15.07.2015
comment
Просто небольшая поправка к вашему примеру кода, вы забыли вывести Derived из Base. Кроме того, +1 за ваш ответ на требование 2. Я подумал, что это как-то связано с нарезкой, я забыл, что виртуальные функции не нарушаются в этом случае, когда задействованы конструкторы копирования. Кроме того, я не верю, что черта is_trivially_copyable требует построения по умолчанию. Я не смог найти ссылку в стандарте (N3337), в которой говорилось бы, что это требование. - person Jared Mulconry; 15.07.2015
comment
Похоже, вы правы в случае с виртуальными базовыми классами. Быстрая проверка простого примера, скомпилированного в VS2015RC, показала, что наличие виртуального базового класса приводит к появлению указателя на виртуальную базовую таблицу, которая присутствует только в производном классе. Кажется, что последует отвратительный эффект нарезки. - person Jared Mulconry; 15.07.2015
comment
Спасибо за исправление, @Jared - я упустил фактическое наследование. Хорошо, что я использовал очевидные имена! Теперь я менее уверен в своем ответе (1), так как он больше связан с operator=, но я оставлю его, так как это может вдохновить кого-то добавить правильный ответ. - person Toby Speight; 15.07.2015
comment
Вероятно, цель состоит в том, чтобы можно было выделить память, скопировать объект и освободить память, и это было бы точно эквивалентно new, operator= и delete? - person Toby Speight; 15.07.2015
comment
@TobySpeight Спасибо за ваш ответ. Теперь предположим, что f() вообще не было, но Derived по-прежнему добавляет элемент (d). Base теперь можно квалифицировать как is_trilly_copyable, но запоминание массива Base&, который на самом деле ссылается на Derived (это то, что вы имели в виду?), создало бы такой же хаос, как если бы присутствовал f(). Я не уверен, как такая копия вообще возможна синтаксически - может быть, вы можете привести игрушечный пример в коде того сценария, который вы имеете в виду? - person Ofek Shilon; 15.07.2015
comment
@Ofek - Нет, если вы memcpy и Derived без виртуальных методов, вы просто получите Base часть Derived. d не будет скопирован, но никто не пострадает, потому что никто не попытается получить к нему доступ. Проблема возникает только из-за возможности доступа к членам Derived через наивно скопированный vtable. - person Toby Speight; 15.07.2015
comment
@TobySpeight: Теперь попробуйте memcpy массив из 10 производных, на которые ссылается Base*, и наблюдайте, как разворачивается хаос. Как я понимаю, is_trily_copyable‹T› следует использовать в качестве теста для запоминания буфера из многих T - запоминать их один за другим кажется бессмысленным. - person Ofek Shilon; 15.07.2015
comment
@TobySpeight, еще одна мысль относительно условия (1): если в месте назначения необходимо вызвать нетривиальный деструктор, не будет ли в любом случае обязательным нетривиальный оператор присваивания? (и поэтому тип будет нетривиально копируемым без дополнительного условия?) - person Ofek Shilon; 15.07.2015
comment
вы можете memcpy из этой переменной в другой базовый объект w Это не то, что [basic.types]p2 позволяет вам делать. Он специально ограничивает гарантию memcpy полными объектами. - person dyp; 15.07.2015
comment
@dyp Точно. Я нашел этот ответ, когда исследовал, можно ли memcpy завершать объекты только с некоторыми членами виртуальной функции. Вы знаете больше? - person Columbo; 14.09.2017