Почему виртуальная функция не может быть не реализована при выделении с помощью 'new'? - PullRequest
8 голосов
/ 03 июня 2011
struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // ok
  obj.bar();  // <-- added this edition
  A* pm = (A*)malloc(sizeof(A)); // ok
  A* pn = new A; // linker error
}

Для объектов в стеке работает нормально . Но для выделения в куче с new (не malloc), это дает ошибку компоновщика:

undefined reference to `vtable for A'

Ответы [ 5 ]

9 голосов
/ 03 июня 2011

Поскольку malloc не вызывает (или не пытается вызвать в этом случае) конструктор A, тогда как new делает.

Этот код компилирует и отмечает, где возникают ошибки компоновщика с GCC:

#include <cstdlib>

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // linker error
  A* pm = (A*) malloc(sizeof(A)); // ok
  A* pn = new A; // linker error
}
6 голосов
/ 03 июня 2011

Во-первых, этот код не компилируется, поскольку в C ++ void * не может быть неявно преобразован в A *.Требуется явное приведение.

Во-вторых, пример с malloc совершенно не имеет значения.malloc выделяет необработанную память, причем абсолютно не имеет отношения к каким-либо конкретным типам.В этом случае malloc знает примечание о любом A и не создает объект типа A.

По этим причинам реальный пример для этого вопроса должен выглядеть следующим образом

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // ok
  A* pn = new A; // linker error
}

И вопрос в том, почему первое объявление не выдает ошибку, в отличие от второго.

С формальной точки зрения ваша программа недействительна, поскольку она нарушает формальные требования языка C ++ (в частности, ODR).На практике оба объявления могут или должны вызывать одну и ту же ошибку, поскольку в обоих случаях объект формально требует указатель на VMT.В этом случае VMT не может быть создан, так как некоторые функции не определены.Однако первое объявление просто проскальзывает только потому, что компилятор смог оптимизировать все ссылки на VMT для первого объявления (а не для второго).Также вполне возможно, что компилятор смог оптимизировать весь объект obj, поскольку на него больше не ссылаются.

В GCC (поскольку вы, похоже, используете GCC), легковызовите ту же ошибку и для первого объявления

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj; // linker error
  A *p = &obj;
  p->bar(); 
}

Приведенный выше код вызовет ту же ошибку компоновщика в GCC, хотя неопределенная функция foo все еще не используется в этом коде.

Другими словами, это просто вопрос добавления достаточного количества кода, чтобы заставить компилятор поверить, что объект VMT необходим.В этом случае разница в поведении между объявлениями не имеет ничего общего с языком C ++.Это просто проблема реализации, специфичная для вашего компилятора.

3 голосов
/ 03 июня 2011

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

Ошибка проявляется именно таким образом из-за специфической реализации vtables в компиляторе.Вы оставили нереализованной виртуальную функцию first .Компилятор вставляет виртуальную таблицу всякий раз, когда видит реализацию первой виртуальной функции класса.Поскольку нет таковых, нет vtable.

Если вы оставите вторую функцию невыполненной, компоновщик будет жаловаться на эту конкретную функцию, а не на vtable.

[edit] Ваш компиляторвероятно, оптимизирована копия A в стеке, поэтому компоновщик не жаловался.

Строка malloc на самом деле не ссылается на объект типа A, поэтому она несоздать проблему с компоновщиком.Есть еще одна проблема с этой строкой: она не должна компилироваться.malloc возвращает void*, который не преобразуется в другие типы указателей без приведения.

1 голос
/ 03 июня 2011

Стандарт требует ровно одну реализацию A::foo, если * где-либо в программе создается экземпляр A.Независимо от того, происходит ли создание экземпляра через объявление локальной переменной или через новое выражение.Однако никакой диагностики не требуется, если это правило нарушено;если вы не предоставляете декларацию, или если вы предоставляете два или более, это просто неопределенное поведение.Все, что делает компилятор, является «правильным».В этом случае, вероятно, происходит следующее:

  • причина, по которой требуется определение, состоит в том, что на него ссылаются в vtable,
  • конструктор Aвстроенный, поэтому код, который инициализирует vptr (и запускает создание экземпляра vtable), полностью виден компилятору,

  • , поскольку все виды использования объекта видны компилятору, онможно увидеть, что vptr никогда не используется, поэтому он просто подавляет его.

  • и без vptr не нужно создавать vtable, поэтому нет ссылки на виртуальную функцию.

В общем, это зависит от того, как оптимизирует компилятор;вы можете получить ошибку как для локального объявления, так и для нового выражения, или ни для одного, ни для одного, а не для другого.И это может зависеть от параметров оптимизации или чего-то еще.Что касается C ++, то это может зависеть от фаз Луны, и вместо ошибки вы можете просто получить код, который вылетал при его запуске (но сценарии, которые я описал вначале, наиболее вероятны).

0 голосов
/ 04 июня 2011

Неиспользованность не имеет значения.Определите все виртуальные функции.Это так просто.

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

...