Новая перегрузка и выравнивание оператора

Я перегружаю operator new, но недавно я столкнулся с проблемой выравнивания. В принципе, у меня есть класс IBase, который предоставляет operator new и delete во всех необходимых вариантах. Все классы производятся от IBase и, следовательно, также используют пользовательские распределители.

Проблема, с которой я сейчас сталкиваюсь, состоит в том, что у меня есть дочерний элемент Foo, который должен быть выровнен по 16 байт, а все остальные - в порядке, когда выровнены с 8-байтовым. Однако мой распределитель памяти выравнивается по 8-байтовым границам только по умолчанию, поэтому теперь код в IBase::operator new возвращает непригодную часть памяти. Как это должно быть правильно решено?

Я могу просто форсировать все выделения до 16 байтов, которые будут работать нормально до тех пор, пока не выскочит 32-байтовый выровненный тип. Выяснить, что выравнивание внутри operator new не кажется тривиальным (могу ли я сделать виртуальную функцию для получения фактического выравнивания?) Какой рекомендуемый способ справиться с этим?

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

Ответ 1

Это возможное решение. Он всегда будет выбирать оператор с самым высоким выравниванием в заданной иерархии:

#include <exception>
#include <iostream>
#include <cstdlib>

// provides operators for any alignment >= 4 bytes
template<int Alignment>
struct DeAllocator;

template<int Alignment>
struct DeAllocator : virtual DeAllocator<Alignment/2> {
  void *operator new(size_t s) throw (std::bad_alloc) {
    std::cerr << "alignment: " << Alignment << "\n";
    return ::operator new(s);
  }

  void operator delete(void *p) {
    ::operator delete(p);
  }
};

template<>
struct DeAllocator<2> { };

// ........... Test .............
// different classes needing different alignments
struct Align8 : virtual DeAllocator<8> { };
struct Align16 : Align8, virtual DeAllocator<16> { };
struct DontCare : Align16, virtual DeAllocator<4> { };

int main() {
  delete new Align8;   // alignment: 8
  delete new Align16;  // alignment: 16
  delete new DontCare; // alignment: 16
}

Это основано на правиле доминирования: если есть неоднозначность в поиске, а двусмысленность - между именами производного и виртуального базового класса, вместо этого берется имя производного класса.


Были подняты вопросы, почему DeAllocator<I> наследует DeAllocator<I / 2>. Ответ заключается в том, что в данной иерархии могут быть разные требования к выравниванию, наложенные классами. Представьте, что IBase не имеет требований к выравниванию, A имеет 8-байтовое требование, а B имеет 16-байтовое требование и наследует A:

class IBAse { };
class A : IBase, Alignment<8> { };
class B : A, Alignment<16> { };

Alignment<16> и Alignment<8> оба отображают operator new. Если вы сейчас скажете new B, компилятор будет искать operator new в B и найдет две функции:

            // op new
            Alignment<8>      IBase
                 ^            /
                  \         /
                    \     /
 // op new            \ /
 Alignment<16>         A
            \         /
              \     /
                \ /
                 B 

B ->      Alignment<16>  -> operator new
B -> A -> Alignment<8> -> operator new

Таким образом, это будет неоднозначно, и мы не скомпилируем: ни один из них не скрывает другого. Но если вы теперь наследуете Alignment<16> практически от Alignment<8> и делаете A и B наследовать их фактически, operator new в Alignment<8> будет скрыто:

            // op new
            Alignment<8>      IBase
                 ^            /
                / \         /
              /     \     /
 // op new  /         \ /
 Alignment<16>         A
            \         /
              \     /
                \ /
                 B 

Это специальное правило скрытия (также называемое правилом доминирования), однако работает только в том случае, если все объекты Alignment<8> одинаковы. Таким образом, мы всегда наследуем фактически: в этом случае существует только один объект Alignment<8> (или 16,...), существующий в любой заданной иерархии классов.

Ответ 2

mixins - правильный подход, однако перегрузка оператора new - нет. Это выполнит то, что вам нужно:

__declspec(align(256))  struct cachealign{};
__declspec(align(4096)) struct pagealign{};
struct DefaultAlign{};
struct CacheAlign:private cachealign{};
struct PageAlign: CacheAlign,private pagealign{};

void foo(){
 DefaultAlign d;
 CacheAlign c;
 PageAlign p;
 std::cout<<"Alignment of d "<<__alignof(d)<<std::endl;
 std::cout<<"Alignment of c "<<__alignof(c)<<std::endl;
 std::cout<<"Alignment of p "<<__alignof(p)<<std::endl;
}

Печать

Alignment of d 1
Alignment of c 256
Alignment of p 4096

Для gcc используйте

struct cachealign{}__attribute__ ((aligned (256)));

Обратите внимание, что существует автоматический выбор наибольшего выравнивания, и это работает для объектов, помещенных в стек, те, которые являются new'd, и как члены других классов. Кроме того, он не добавляет никаких виртуальных машин и не предполагает, что EBCO не имеет дополнительного размера для класса (вне поля, необходимого для самого выравнивания).

Ответ 3

Используя Visual Studio Express 2010, приведенный выше пример не работает с новым:

CacheAlign *c = new CacheAlign;

даст значение __align_of (c) == 4 (предположительно я предполагаю), но адрес первого члена CacheAlign не выравнивается по запросу.

Не последний вопрос, но если я правильно понимаю ОП, у него есть классы, которые являются дочерними элементами родительского класса, которые определяют распределитель и освободитель, и все они могут потребовать определенного выравнивания. Что не так с тем, что у вас есть простой новый, который вызывает какой-то частный распределитель, который выполняет фактическую работу и получает аргумент выравнивания - общую версию с выравниванием по умолчанию в родительском классе, унаследованном или перегруженном версией, которая указывает правильное выравнивание?