В каких обстоятельствах выгодно давать реализацию чистой виртуальной функции?

В С++ законно давать реализацию чистой виртуальной функции:

class C
{
public:
  virtual int f() = 0;
};

int C::f() 
{
  return 0;
}

Зачем вам это делать?

Связанный вопрос: С++ faq lite содержит пример:

class Funct {
public:
  virtual int doit(int x) = 0;
  virtual ~Funct() = 0;
};

inline Funct::~Funct() { }  // defined even though it pure virtual; it faster this way; trust me

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

Ответ 1

Объявленные деструкторы всегда должны быть реализованы, поскольку реализация вызовет их как часть разрушения производного объекта.

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

void Derived::f()
{
    Base::f();

    // Other Derived specific functionality
}

Как правило, вы создаете виртуальный деструктор, если вам нужно создать абстрактный класс (т.е. предотвратить создание не производных экземпляров), но класс не имеет других функций, которые естественным образом являются виртуальными. Я думаю, что "верьте мне это быстрее" ссылается на тот факт, что, поскольку деструкторы, называемые частью очистки производного объекта, не нуждаются в использовании механизма поиска vtable, встроенная реализация может быть использована в отличие от типичных вызовов виртуальных функций.

Ответ 2

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

Таким образом, производный класс реализует виртуальную функцию и вызывает базовую версию:

class X: public C
{
    public:
        virtual int f()
        {
            return C::f() + 1; // I am +1 over my parent.
        }
};

Ответ 3

Просто выяснилось, что Херб Саттер ответил на первую часть этого вопроса в своем Гуру недели № 31.

Ответ 4

G'day,

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

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

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

А, только что увидел пост @Martin York, который дает пример.

Собственно, Скотт Мейерс обсуждает это в своей книге "Эффективный C++". Это пункт 36 в 1-м издании.

НТН

веселит,

Ответ 5

Поскольку он считал плохо сформированным, чтобы написать:

class Funct {
public:
  virtual int doit(int x) = 0;
  virtual ~Funct() = 0 {};
};

Деструктор все равно будет вызван, если вы выйдете из этого класса. Объявление всех методов чисто виртуальным просто для ясности. Вы могли бы написать так:

class Funct {
public:
  virtual int doit(int x) = 0;
  virtual ~Funct() {};
};

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

Ответ 6

Что касается скорости виртуального деструктора, это связано с тем, что деструктор определен в файле cpp, а не в заголовке. Это больше связано с размером, чем с скоростью. Это подробно объясняется в "Крупномасштабном программном обеспечении С++". К сожалению, я не могу запомнить все детали, но я думаю, что встроенные виртуальные функции определяются несколько раз в vtable.

Здесь обсуждается следующее: Действительно ли встроенные виртуальные функции не имеют смысла?