Что скомпилировал MS Visual C++ 7?
От: McKolby  
Дата: 22.11.05 15:01
Оценка:
Хотелось бы узнать, почему не совпадает мнения у Visual C++ 7 с остальными компиляторами?
А именно, для MS Visual C++ 7 m_c->n::A::Number(); не эквивалентно ((n::A*)m_c)->Number(); в приведённом ниже примере. В результате выполнения строки кода m_c->n::A::Number(); вместо вызова виртуального метода родителя происходит вызов неизвестно чего. Зайдя в дебагере во внутрь этого вызова, видим что указател this смещён на 4 байта.
Избежать проблему можно несколькими способами, но хочу понять именно эту ситуацию.
Варианты правок, чтобы код работал правильно:
— Отказаться от namespace
— В классе I сделать функцию foo() невиртуальной
— При объявлении класса C поменять порядок родительских классов (класс I поставить последним)
— Заменить вызов m_c->n::A::Number(); на ((n::A*)m_c)->Number();
А чтобы вообще получить access violation нужно в исходном примере добавить в класс I член класса (например int i;).
Уважаемые, объясните пожалуйста, как Visual C++ понимает такой вызов? Что он пытается сделать?

#include <iostream>

using namespace std;

namespace n
{
    class A
    {
    public:
        void Number() const
        {
            A_Number();
        };
    private:
        virtual void A_Number() const = 0;
    };

    class B
    {
    public:
        void Number() const
        {
            B_Number();
        };
    private:
        virtual void B_Number() const = 0;
    };

}

class I
{
public:
    virtual void foo()
    {
    }
};


class C : public I, public n::A, public n::B
{
private:
    virtual void  A_Number() const 
    {
        cout << "C::A_Number";
    };
    virtual void B_Number() const 
    {
        cout << "C::B_Number";
    };
};

class D: public n::A, public n::B
{
public:
    D()
    {
        m_c = new C;
    };
    ~D()
    {
        delete m_c;
        m_c = NULL;
    };
private:
    C* m_c;
    virtual void A_Number() const
    {
        cout << "D::A_Number -> ";
        m_c->n::A::Number();          // only  ((n::A*)m_c)->Number();  work ok!
        cout << endl;
    }
    virtual void B_Number() const
    {
        cout << "D::B_Number -> ";
        m_c->n::B::Number();          // only  ((n::B*)m_c)->Number();  work ok!
        cout << endl;
    }
};

int main()
{
    D d;

    d.n::A::Number();
    d.n::B::Number();

    return 0;
}
Re: Что скомпилировал MS Visual C++ 7?
От: Bell Россия  
Дата: 22.11.05 17:23
Оценка:
Здравствуйте, McKolby, Вы писали:

MK>Хотелось бы узнать, почему не совпадает мнения у Visual C++ 7 с остальными компиляторами?

MK>А именно, для MS Visual C++ 7 m_c->n::A::Number(); не эквивалентно ((n::A*)m_c)->Number(); в приведённом ниже примере. В результате выполнения строки кода m_c->n::A::Number(); вместо вызова виртуального метода родителя происходит вызов неизвестно чего. Зайдя в дебагере во внутрь этого вызова, видим что указател this смещён на 4 байта.

Попробовал на VC++ 7.1. Проблема заключается в том, что при вызове m_c->n::A::Number(); this не сдвигается на 4 байта (сдвиг должен происходить из-за того, что класс I в списке наследования стоит до A). В результате используется vtbl сласса I, последсвия чего могут быть самыми разнообразными.
При явном касте ((n::A*)m_c)->Number(); производится сдвиг this , и вызывается правильный метод.
(Кстати тут лучше явно использовать static_cast. В данном примерер, конечно, (n::A*)m_c разворачивается именно в static_cast<n::A*>, но береженого Бог бережет...)

MK>Избежать проблему можно несколькими способами.

...
ИМХО лучше отказаться от избыточной квалификации:

m_c->n::A::Number() --> m_c->A::Number();
m_c->n::B::Number() --> m_c->B::Number();


При таком исправлении на VC++ 7.1 все работает.

ЗЫ
Мне лень копаться в стандарте и искать, является ли такая "расширенная" квалификация допустимой Однако ИМХО такой синтаксис вполне допустим, и мы имеем дело с багом компилятора.
Любите книгу — источник знаний (с) М.Горький
Re: Что скомпилировал MS Visual C++ 7?
От: Павел Кузнецов  
Дата: 22.11.05 17:47
Оценка:
McKolby,

> Хотелось бы узнать, почему не совпадает мнения у Visual C++ 7 с остальными компиляторами?


Ошибка.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re: Что скомпилировал MS Visual C++ 7?
От: Dmi_3 Россия  
Дата: 22.11.05 18:37
Оценка: 2 (2)
Здравствуйте, McKolby, Вы писали:

MK>Уважаемые, объясните пожалуйста, как Visual C++ понимает такой вызов? Что он пытается сделать?


Всё становится понятным если переписать пример и "поиграться" с define

#include <iostream>

using namespace std;

//#define CALL_AND_CAST(TYPE,DATA) static_cast     <TYPE*>(DATA)->
//#define CALL_AND_CAST(TYPE,DATA) reinterpret_cast<TYPE*>(DATA)->
//#define CALL_AND_CAST(TYPE,DATA) ((TYPE*)(DATA))->
#define CALL_AND_CAST(TYPE,DATA) DATA->TYPE::

#define INHERITED_LIST_C public n::B, public I, public n::A
#define INHERITED_LIST_D public n::A, public n::B

namespace n {

    class A {
    public:
        void Number() const { A_Number(); };
        virtual void A_Number() const = 0;
    };

    class B {
    public:
        void Number() const { B_Number(); };
        virtual void B_Number() const = 0;
    };

}

class I {
    virtual void foo(){ cout << "I::foo"; };
};

class C : INHERITED_LIST_C {
    virtual void A_Number() const { cout << "C::A_Number"; };
    virtual void B_Number() const { cout << "C::B_Number"; };
};

class D: INHERITED_LIST_D {
private:
    C* m_c;
    virtual void A_Number() const { CALL_AND_CAST(n::A,m_c) Number(); }
    virtual void B_Number() const { CALL_AND_CAST(n::B,m_c) Number(); }
public:
    D() : m_c(new C) {}
    ~D() { delete m_c; }
};

int main() {
    D* d = new D;
    cout << "D::A_Number -> ";
    CALL_AND_CAST(n::A,d) Number();
    cout << endl;
    cout << "D::B_Number -> ";
    CALL_AND_CAST(n::B,d) Number();
    cout << endl;
    delete d;
    return 0;
}
Re[2]: Что скомпилировал MS Visual C++ 7?
От: Dmi_3 Россия  
Дата: 22.11.05 22:31
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>Ошибка.


Вот более "минимальный" пример.
struct A {
    void method() {
        A_method();
    }
    virtual void A_method() {
    }
};

struct B {
    void method() {
        B_method();
    }
    virtual void B_method() {
    }
};

struct C:A,B{
    void A_method() {
    }
    void B_method() {
    }
};

struct D:B,A{//Именно в обратном порядке чем у struct C
    C* ptr;
    void A_method() {
        ptr->::A::method();//Здесь один _cast
        ptr->  A::method();//Здесь другой _cast
    }
    D() : ptr(new C) {}
    ~D() {delete ptr;}
};

int main() {
    D().A::method();
    return 0;
}


Микрософт "славится" такими штучками.
Например в VC6
template<int N>
struct qqq{
    enum www{ value = N };
};

enum eee{
    A = qqq<1>::value,
    B = qqq<2>::value,
};

Тип A был eee, а тип B был qqq<1>::www
Для типов значений enum использовался тип последнего откомпилированного enum
Re[2]: Что скомпилировал MS Visual C++ 7?
От: Alexeib Япония  
Дата: 23.11.05 06:38
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>McKolby,


>> Хотелось бы узнать, почему не совпадает мнения у Visual C++ 7 с остальными компиляторами?


ПК>Ошибка.


Pochemu bug ne bil opublikovan samim avtorom? Poluchaetsya svoego roda narushenie avtorskogo prava.
Re[3]: Что скомпилировал MS Visual C++ 7?
От: McKolby  
Дата: 23.11.05 07:04
Оценка:
Здравствуйте, Alexeib, Вы писали:

A>Pochemu bug ne bil opublikovan samim avtorom? Poluchaetsya svoego roda narushenie avtorskogo prava.


К сожалению я не знал о такой возможности.
Отдельное спасибо Павлу Кузнецову за столь подробный и развёрнутый ответ.
Re[4]: Что скомпилировал MS Visual C++ 7?
От: Павел Кузнецов  
Дата: 30.11.05 23:55
Оценка:
McKolby,

> Отдельное спасибо Павлу Кузнецову за столь подробный и развёрнутый ответ.


Искренне прошу прощения. Я не подумал, что для Вас могло быть важным сообщить об ошибке лично. В качестве утешения могу сказать, что ошибку разработчики признали.
Posted via RSDN NNTP Server 2.0
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.