Указатель на виртуальную функцию-член действителен в конструкторе базового класса? - PullRequest
12 голосов
/ 22 июня 2010

Мой вопрос не о вызове виртуальной функции-члена из конструктора базового класса, а о том, действителен ли указатель на виртуальную функцию-член в конструкторе базового класса.

Учитывая следующее

class A
{
    void (A::*m_pMember)();

public:
    A() :
        m_pMember(&A::vmember)
    {
    }

    virtual void vmember()
    {
        printf("In A::vmember()\n");
    }

    void test()
    {
        (this->*m_pMember)();
    }
};

class B : public A
{
public:
    virtual void vmember()
    {
        printf("In B::vmember()\n");
    }
};

int main()
{
    B b;
    b.test();

    return 0;
}

Будет ли это производить "In B :: vmember ()" для всех совместимых компиляторов c ++?

Ответы [ 6 ]

3 голосов
/ 22 июня 2010

Указатель действителен, однако вы должны иметь в виду, что когда виртуальная функция вызывается через указатель, она всегда разрешается в соответствии с типом динамического объекта, используемого слева.боковая сторона.Это означает, что когда вы вызываете виртуальную функцию из конструктора, не имеет значения, вызываете ли вы ее напрямую или вызываете через указатель.В обоих случаях вызов будет преобразован в тип, конструктор которого в данный момент работает.Вот как работают виртуальные функции, когда вы вызываете их во время конструирования (или уничтожения) объекта.

Обратите также внимание, что указатели на функции-члены, как правило, не привязываются к конкретным функциям в момент инициализации.Если целевая функция не виртуальная, можно сказать, что указатель указывает на конкретную функцию.Однако, если целевая функция является виртуальной, невозможно сказать, куда указывает указатель.Например, в спецификации языка прямо указано, что при сравнении (на равенство) двух указателей, которые указывают на виртуальные функции, в результате получается unspecified .

3 голосов
/ 22 июня 2010

«Действительный» - это конкретный термин применительно к указателям. Указатели данных действительны, когда они указывают на объект или NULL; указатели на функции действительны, когда они указывают на функцию или NULL, а указатели на элементы действительны, когда указатель на элемент или NULL.

Тем не менее, из вашего вопроса о фактических результатах я могу сделать вывод, что вы хотели спросить что-то еще. Давайте посмотрим на вашу vmember функцию - или я должен сказать функции? Очевидно, есть два функциональных тела. Вы могли бы сделать только один производный виртуальным, так что это также подтверждает, что на самом деле есть две vmember функции, которые обе оказываются виртуальными.

Теперь возникает вопрос, выбирает ли адрес функции-члена уже действительную функцию. Ваши реализации показывают, что они этого не делают, и что это происходит только тогда, когда указатель действительно разыменовывается.

Причина, по которой должен работать таким образом, тривиальна. Взятие адреса функции-члена не затрагивает фактический объект, то, что потребуется для разрешения виртуального вызова. Позвольте мне показать вам:

namespace {
  void (A::*test)() = &A::vmember;
  A a;
  B b;
  (a.*test)();
  (b.*test)();
}

Когда мы инициализируем test, объект типа A или B вообще не существует, но возможно взять адрес &A::vmember. Этот тот же самый указатель члена может тогда использоваться с двумя различными объектами. Что это может произвести, кроме "In A :: vmember () \ n" и "In B :: vmember () \ n"?

1 голос
/ 22 июня 2010

Я нашел небольшое объяснение по поводу Старого нового (блог Рэймонда Чена, иногда называемого Чаком Норрисом из Microsoft).

Конечно, это ничего не говорит о соответствии, но объясняет, почему:

B b;

b.A::vmember(); // [1]

(b.*&A::vmember)(); // [2]

1 и 2 фактически вызывают другую функцию ... что довольно удивительно, правда. Это также означает, что вы не можете предотвратить отправку во время выполнения, используя указатель на функцию-член: /

1 голос
/ 22 июня 2010

Прочитайте эту статью для подробного обсуждения указателей на функции-члены и как их использовать.Это должно ответить на все ваши вопросы.

0 голосов
/ 22 июня 2010

IMO implementation defined, чтобы получить адрес виртуальной функции.Это связано с тем, что виртуальные функции реализованы с использованием vtables , которые зависят от реализации компилятора.Поскольку не гарантируется, что vtable будет завершен до тех пор, пока не будет выполнено выполнение ctor класса, указатель на запись в такой таблице (виртуальная функция) может быть implementation defined behavior.

Есть несколько связанный вопрос, который я задал на SO здесь несколько месяцев назад;который в основном говорит, что получение адреса виртуальной функции не указано в стандарте C ++.

Таким образом, в любом случае, даже если он работает для вас, решение не будет переносимым.

0 голосов
/ 22 июня 2010

Я думаю, что нет.Указатель на виртуальную функцию-член разрешается через VMT, так что происходит так же, как и вызов этой функции.Это означает, что это недопустимо, так как VMT заполняется после завершения конструктора.

...