Ошибка виртуального наследования в MSVC

Кажется, что моя проблема - ошибка в MSVC. Я использую Visual Studio 2008 с пакетом обновления 1, а мой код работает с GCC (как проверено на codepad.org).

Любая официальная информация об этой ошибке? Есть идеи, как обойти это? Исправлена ​​ли ошибка в VS2010? Все идеи были бы оценены.

Код:

struct Base {
    Base(int i = 0) : i(i) {}
    virtual ~Base() {}
    virtual Base *clone() const = 0;

protected:
    int i;
};

struct A : virtual public Base {
    A() {}
    virtual A *clone() const = 0;
};

struct B : public A {
    B() {}
    B *clone() const { return new B(*this); }

    /// MSVC debugger shows that 'b' is for some reason missing the Base
    /// portion of it object ("Error: expression cannot be evaluated")
    /// and trying to access 'b.i' causes an unhandled exception.
    ///
    /// Note: This only seems to occur with MSVC
    B(const B &b) : Base(b.i), A() {}
};

void foo(const A &elem) {
    A *a = elem.clone();
    if (a) delete a;
}

int main() {
    A *a = new B;
    foo(*a);
    delete a;
}

Ответ 1

Похоже, что компилятор неправильно корректирует указатель this при вызове A::clone. Если вы удалите объявление A::clone, тогда все будет хорошо.

Копаясь глубже, когда у вас A::clone, vtable выглядит следующим образом:

    [0x0]   0x002f1136 [thunk]:B::`vector deleting destructor'`vtordisp{4294967292,0}' (unsigned int)   void *
    [0x1]   0x002f11e0 [thunk]:B::clone`vtordisp{4294967292,0}' (void)  void *
    [0x2]   0x002f12ad [thunk]:B::clone`vtordisp{4294967292,4}' (void)  void *
    [0x3]   0x002f12a3 B::clone(void)   void *

И foo вызывает elem.__vfptr[2], смещая this неправильно на -4 байта. Без A::clone vtable выглядит так:

    [0x0]   0x00ee1136 [thunk]:B::`vector deleting destructor'`vtordisp{4294967292,0}' (unsigned int)   void *
    [0x1]   0x00ee11e0 [thunk]:B::clone`vtordisp{4294967292,0}' (void)  void *
    [0x2]   0x00ee12a3 B::clone(void)   void *

И foo вызывает elem.__vfptr[1]. Это не настраивает this вообще (и код предполагает, что this будет равен Base вместо B).

Итак, похоже, что компилятор предполагает, что A::clone является новым виртуальным методом и не переопределяет Base::clone при определении того, нужна ли A новая виртуальная таблица, но затем какой-то другой код позже определяет, что A не требуется виртуальная таблица. Вы можете проверить это, сравнив sizeof(B) с новой виртуальной функцией или без нее:

struct A : virtual public Base {
    A() {}
    virtual A *clone() const = 0;
}; //sizeof(B)==16

struct A : virtual public Base {
    A() {}
    virtual A *clone() const = 0;
virtual const A *clone2() const { return this; }
}; //sizeof(B)==20

Итак, это ошибка компилятора.

Ответ 2

Казалось бы (из некоторого тестирования), что ошибка вызвана комбинацией виртуального базового класса с чистым виртуальным методом с использованием ковариантных типов возврата.

Сбрасывая либо чистый виртуальный метод из класса Base, либо делая Base не виртуальным базовым классом, либо делая метод clone() несовместимым, кажется, решает проблему.

Я предполагаю, что этот вопрос решил для меня (после того, как я отправлю отчет об ошибках в MS), и я даже остался с несколькими вариантами, чтобы обойти его.:)