Стоит ли когда-нибудь ставить виртуальные методы на копируемый тип?

Видели некоторые связанные вопросы, но не этот точный …

Я отнес классы к нескольким основным категориям, скажем, для простоты:

  • Классы значений которые имеют некоторые данные и кучу операций. Их можно скопировать и сравнить по значимости на равенство (при этом ожидается, что копии будут равны ==). Этим почти всегда не хватает виртуальных методов.

  • Уникальные Классы чьи экземпляры имеют идентичность, для которой вы отключаете назначение и копирование. Там обычно нет operator== на них, потому что вы сравниваете их как указатели, а не объекты. У них довольно часто есть много виртуальных методов, так как нет риска так как вы вынуждены передавать их по указателю или по ссылке.

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

  • Контейнерные классы которые наследуют свойства того, что они держат. Они, как правило, не имеют виртуальных методов … см. Например «Почему в контейнерах STL нет виртуальных деструкторов?».

Независимо от наличия этой неформальной системы убеждений, пару раз я пытался добавить виртуальный метод к чему-то копируемому. Хотя я, возможно, думал, что было бы «действительно здорово, если бы это сработало», это неизбежно сломается.

Это заставило меня задуматься, есть ли у кого-нибудь действительно хороший пример типа, который имеет виртуальные методы и не отключает копирование?

18

Решение

Нет ничего плохого в том, чтобы копировать полиморфный класс. Проблема заключается в возможности скопировать не листовой класс. Нарезка объектов поможет вам.

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

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

Полиморфные классы, как правило, не являются «классами значений», но это случается. std::stringstream приходит на ум. Это не для копирования, но это подвижно (в C ++ 11) и перемещение ничем не отличается от копирования в отношении нарезки.

6

Другие решения

Единственный встречный пример, который у меня есть, это классы, предназначенные для размещения в стеке, а не в куче. Одна схема, для которой я ее использую, — это внедрение зависимостей:

class LoggerInterface { public: virtual void log() = 0; };

class FileLogger final: public LoggerInterface { ... };

int main() {
FileLogger logger("log.txt");

callMethod(logger, ...);
}

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

Тем не менее, это может быть просто final оказалось FileLogger в класс значения.

Примечание: я знаю, копирование логгера кажется странным …

7

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

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

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

С учетом сказанного теперь ясно, что ваш код будет иметь дело в основном с полиморфным база классы, например в интерфейсах функций или в контейнерах. Итак, давайте перефразируем вопрос: должен ли такой базовый класс быть копируемым? Ну, поскольку у вас никогда не будет реальных, наиболее производных базовых объектов (то есть базовый класс по сути является абстрактным), это на самом деле не проблема, и в этом нет необходимости. Вы уже упомянули идиому «клон», которая является подходящим аналогом копирования в полиморфном.

Теперь функция «клон» обязательно реализуется в каждом листовом классе, и для нее обязательно требуется копирование листовых классов. Так что да, каждый конечный класс в клонируемой иерархии — это класс с виртуальными функциями а также конструктор копирования. А поскольку конструктор копирования производного класса должен копировать свои базовые подобъекты, все базы также должны быть копируемыми.

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

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

Стоит ли делать копию с базовой ссылки? Возможно нет.

3

Не с одного, а с двумя классами:

#include <iostream>
#include <vector>
#include <stdexcept>

class Polymorph
{
protected:
class Implementation {
public:
virtual ~Implementation() {};
// Postcondition: The result is allocated with new.
// This base class throws std::logic error.
virtual Implementation* duplicate() {
throw std::logic_error("Duplication not supported.");
}

public:
virtual const char* name() = 0;
};

// Precondition: self is allocated with new.
Polymorph(Implementation* self)
:   m_self(self)
{}

public:
Polymorph(const Polymorph& other)
:   m_self(other.m_self->duplicate())
{}

~Polymorph() {
delete m_self;
}

Polymorph& operator = (Polymorph other) {
swap(other);
return *this;
}

void swap(Polymorph& other) {
std::swap(m_self, other.m_self);
}

const char* name() { return m_self->name(); }

private:
Implementation* m_self;
};

class A : public Polymorph
{
protected:
class Implementation : public Polymorph::Implementation
{
protected:
Implementation* duplicate() {
return new Implementation(*this);
}

public:
const char* name() { return "A"; }
};

public:
A()
:   Polymorph(new Implementation())
{}
};

class B : public Polymorph {
protected:
class Implementation : public Polymorph::Implementation {
protected:
Implementation* duplicate() {
return new Implementation(*this);
}

public:
const char* name() { return "B"; }
};

public:
B()
:   Polymorph(new Implementation())
{}
};int main() {
std::vector<Polymorph> data;
data.push_back(A());
data.push_back(B());
for(auto x: data)
std::cout << x.name() << std::endl;
return 0;
}

Примечание. В этом примере объекты копируются всегда (хотя вы можете реализовать общую семантику)

2
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector