Ответ 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,...), существующий в любой заданной иерархии классов.