Это возможное решение. Он всегда будет выбирать оператора с самым высоким выравниванием в данной иерархии:
#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, ...) объект.