Объявление пустого деструктора не позволяет компилятору вызывать memmove () для копирования смежных объектов. - PullRequest
0 голосов
/ 11 сентября 2018

Рассмотрим следующее определение Foo:

struct Foo {
    uint64_t data;
};

Теперь рассмотрим следующее определение Bar, которое имеет тот же элемент данных, что и Foo, но имеет пусто деструктор, объявленный пользователем :

struct Bar {
    ~Bar(){} // <-- empty user-declared dtor
    uint64_t data; 
};

При использовании gcc 8.2 с -O2 функция copy_foo():

void copy_foo(const Foo* src, Foo* dst, size_t len) {
    std::copy(src, src + len, dst);
}

приводит к следующемукод сборки:

copy_foo(Foo const*, Foo*, size_t):
        salq    $3, %rdx
        movq    %rsi, %rax
        je      .L1
        movq    %rdi, %rsi
        movq    %rax, %rdi
        jmp     memmove
.L1:
        ret

Приведенный выше код сборки вызывает memmove(), чтобы выполнить копирование смежных объектов Foo.Однако функция ниже, copy_bar(), которая точно так же, как copy_foo(), но для Bar объектов:

void copy_bar(const Bar* src, Bar* dst, size_t len) {
    std::copy(src, src + len, dst);
}

генерирует следующий код сборки:

copy_bar(Bar const*, Bar*, size_t):
        salq    $3, %rdx
        movq    %rdx, %rcx
        sarq    $3, %rcx
        testq   %rdx, %rdx
        jle     .L4
        xorl    %eax, %eax
.L6:
        movq    (%rdi,%rax,8), %rdx
        movq    %rdx, (%rsi,%rax,8)
        addq    $1, %rax
        movq    %rcx, %rdx
        subq    %rax, %rdx
        testq   %rdx, %rdx
        jg      .L6
.L4:
        ret

Этот ассемблерный код не вызывает memmove(), но выполняет копирование само по себе.

Конечно, если вместо Bar определено следующее:

struct Bar {
    ~Bar() = default; // defaulted dtor
    uint64_t data;
};

Тогда обе функцииВ результате получается идентичный ассемблерный код, поскольку Foo также имеет дефолтный деструктор.

Есть ли причина, по которой объявление пользователем пустого деструктора в классе не позволяет компилятору генерировать вызов для memmove() для копирования смежныхобъекты этого класса?

Ответы [ 4 ]

0 голосов
/ 11 сентября 2018

Документация для std::memmove гласит:

Если объекты не TriviallyCopyable, поведение memmove не указано и может быть неопределенным.

TriviallyCopyable требует, чтобы:

  • Каждый конструктор копирования тривиален или удален
  • Каждый конструктор движения тривиален или удален
  • Каждый оператор присвоения копии тривиален или удален
  • Каждый оператор назначения перемещения тривиален или удален по крайней мере один конструктор копирования, конструктор перемещения, оператор назначения копирования или оператор назначения перемещения не удален
  • Тривиальный не удаляемый деструктор

A Тривиальный деструктор требует, чтобы:

  • Деструктор не предоставляется пользователем (имеется в виду, что он либо неявно объявлен, либо явно определен как дефолтный в своем первом объявлении)
  • Деструктор не является виртуальным (то есть деструктор базового класса не является виртуальным)
  • Все прямые базовые классы имеют тривиальные деструкторы
  • Все нестатические члены-данные типа класса (или массива типа класса) имеют тривиальные деструкторы

При добавлении деструктора, предоставленного пользователем, ваш тип больше не может быть просто скопирован, и передача его в std::memmove является неопределенным или неопределенным поведением.

0 голосов
/ 11 сентября 2018

std::memmove можно использовать только для объектов, которые TriviallyCopyable , для которых требуется тривиальный деструктор .Тривиальные деструкторы требуют, чтобы деструктор не был предоставлен пользователем.

В вашем коде для Bar:

struct Bar {
    ~Bar(){} // <-- empty user-declared dtor
    uint64_t data; 
};

Деструктор предоставляется пользователем, поэтому Bar не TriviallyCopyable .Таким образом, в общем случае некорректно для компилятора генерировать вызов std::memmove.


По правилу «как будто» компилятор теоретически может обнаружить, что деструктор пуст и, следовательно, эквивалентентривиально, но очевидно, что эта оптимизация не включена в реализацию std::copy.

В реализации std::copy из libstdc ++ используется эквивалент std::is_trivially_copyable, который определен для отчета Bar как нетривиально копируемый.Включение этой оптимизации потребовало бы, чтобы libstdc ++ имел особую особенность типа для обнаружения этого особого случая, которого легко избежать, написав ~Bar() = default;

0 голосов
/ 11 сентября 2018

Когда вы объявляете свой собственный деструктор, этот класс больше не является тривиально разрушаемым или копируемым. std::memmove требует, чтобы передаваемый объект был тривиально копируемым, чтобы его больше нельзя было использовать в классе.

Стандарт не предъявляет к реализации требования проверять и проверять, является ли ваш деструктор на самом деле нетривиальным, он просто устанавливает все пользовательские деструкторы по умолчанию на нетривиальные.

Если ваш деструктор действительно тривиален, то нет смысла его писать.

0 голосов
/ 11 сентября 2018

Нет реального объяснения этому.Объявление пользовательского деструктора (даже пустого) делает класс не тривиально разрушаемым.(не POD на старом языке).

Компилятор, похоже, не способен понять тот факт, что деструктор ничего не делает и его можно опустить.Вы можете назвать это отсутствующей оптимизацией, если хотите.

...