Не виртуальная деривация: что я действительно получаю от компилятора?

Мне интересно, что создается компилятором при использовании не виртуальной деривации:

template< unsigned int D >
class Point
{
     int[D];
    // No virtual function
    // ...
};
class Point2 : public Point<2> {};
class Point3 : public Point<3> {};

Подразумевает ли здесь вывод только проверки во время компиляции? Или есть какие-то другие накладные расходы?

Я заметил, что мой компилятор производит объекты одинакового размера при использовании Point2 или напрямую Point<2>, Я делаю вывод, что вывод не привел vtable, и, как следствие, виртуальный вызов никогда не будет сделан.

Я что-то пропустил?


контекст

Я хочу предоставить пару предопределенных специализаций данного шаблона класса. Я начал с typedefs:

template< unsigned int D >
class Point
{
     int[D];
};
typedef Point<2> Point2;
typedef Point<3> Point3;

Увы, это не позволяет клиентам использовать "простые" предварительные объявления:

// No #include <Point.h>
class Point2;    // 'Point2': redefinition; different basic types
class Point3;    // 'Point3': redefinition; different basic types

Тогда обязательно написать этот довольно не интуитивный фрагмент кода:

// No #include <Point.h>
template< unsigned int > class Point;
typedef Point<2> Point2;
typedef Point<3> Point3;

Вот почему я отказался от typedefs и использовал не виртуальную деривацию. Тем не менее, мне интересно, каковы все последствия.

(Другой стратегией было бы написать предварительную декларацию один раз в отдельном заголовочном файле, например, #include <iosfwd>.)

4 ответа

Решение

Хорошо, похоже, никто еще не дал вам фактический ответ на ваш вопрос:

Нет, нет никаких накладных расходов на не виртуальную деривацию. Компилятору не нужно создавать vtable, нет вызовов виртуальных функций, и все хорошо. Обычно это реализуется просто размещением экземпляра базового класса в начале производного класса, так что указатель на производный класс также может рассматриваться как указатель на базовый класс. И тогда все просто работает.

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

Однако, если вы используете несколько базовых классов, это может привести к небольшим накладным расходам (в зависимости от того, как компилятор это реализует). Вероятно, не очень (this указатель должен время от времени корректироваться), но теоретически он есть.

Я всегда иду с предварительным объявлением (typedef) по следующим причинам:

  1. Наследование является альтернативой дженерикам.

Я действительно не вижу, в чем проблема с использованием typedefs здесь. Это то, для чего предназначен typedef. Я считаю, что есть несколько ограничений при работе в пространствах имен, но не похоже, что вы делаете это здесь. Довольно часто можно увидеть что-то вроде этого:

General.h

#include <map>
#include <string>

class MyObj1;
class MyObj2;

typedef map< string, MyObj1 > MyObj1Map;
typedef map< string, MyObj2 > MyObj2Map;

Затем вы можете включить это в свой исходный код, хотя вам придется помнить, чтобы включить определение MyObj1 а также MyObj2 где компилятор должен знать размер (т. е. кроме ссылок и указателей в объявлениях.)

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

Стандартное решение (я не понимаю, почему вы этого не хотите) - это выделенный заголовок:

// File PointFwd.h
#ifndef POINT_FWD_H
#define POINT_FWD_H 1

template <unsigned> class Point;

typedef Point<2> Point2;
typedef Point<3> Point3;

#endif

Ваши клиенты должны только включить "PointFwd.h" вперед объявить, что они хотят.

Другие вопросы по тегам