class Foo {
public:
Foo (int);
private:
Foo(const &Foo);
};
и следующий код:
Foo f0(1);
Foo f1=1;
Скомпилируются они почти наверняка в одно и то же, но, если первая строчка всегда вызывает конструктор foo(int)
, то вторая формально представляет собой Foo(Foo(int))
. Однако, компилятору разрешено оптимизировать код в вызов первого конструктора, что он, как правило и делает. Но современный компилятор увидит ошибку во второй записи, так как формально вызывается приватный конструктор Foo(const &Foo);
, однако, после разрешения использовать второй конструктор, пользоваться им всё равно не будет. Вот такая история.
class Translator {
public:
std::string translate(const std::string&);
};
и тот, кто потом будет этим пользоваться, в нужный момент напишет у себя:
Translator t;
text = t.translate("...");
Здесь и возникает проблема: переводчик должен переводить с любого языка на любой, но, если узнать с какого языка переводить ещё как-нибудь можно, то узнать на какой весьма проблематично.
std::string translate(const std::string&, const Language& from, const Language& to);
Однако переводчики с разных языков могут существенно различаться, и в функции translate для каждой пары языков должен быть свой кусок кода, то есть в ней сначала должен быть switch по from, а потом по to, который, во-первых, будет очень большим, а, во-вторых, для добавления нового языка его надо будет прописать во все ветки, что может очень легко привести к ошибкам. То есть, этот способ плох.
TranslatorEnFr t;
text = t.translate("...");
break;
Тут можно заметить, что вторая строчка во всех случаях одинакова, и её так и хочется вытащить из switch. Можно сказать, что у всех переводчиков одинаковый интерфейс, что хотелось бы использовать. Итак –
class Translator {
public:
virtual std::string translate(const std::string&);
};
Этот класс сам ничего не делает и в других языках называется интерфейсом. На его основе создаются уже частные случаи:
class TranslatorKrJp: public Translator {
public:
std::string translate(const std::string&);
};
Класс "Переводчик с корейского на японский" наследуется от класса "переводчик". Также говорят, что "Переводчик с корейского на японский" называется наследником или сыном, а "переводчик" – предком, отцом или базовым классом. Тогда можно писать так:
Translator* t = new TranslatorKrJp();
text = t->translate("...");
При этом вызовется функция translate класса TranslatorKrJp, а не Translator. Почему же?
class Foo {
private:
int myF1, myF2;
};
Когда создаётся объект класса foo, он представляет собой в памяти просто int, и его размер – 2 размера int.
class Base {
private:
int myF1;
};
class Derived: public Base {
private:
int myF2;
};
Base представляет собою просто int, а Derived содержит все поля из Base и ещё свои. Поэтому синоним наследования – расширение. Если же есть виртуальная функция, то объект становится больше на один указатель – указатель на таблицу виртуальных функций.
class Translator {
public:
virtual std::string translate(const std::string&);
virtual const language &from();
virtual const language &to();
};
В каждом объекте типа Translator присутствует указатель на общую для класса таблицу указателей на функции.Translator::translate |
Translator::from |
Translator::to |
TranslatorKrJp::translate |
TranslatorKrJp::from |
TranslatorKrJp::to |
virtual std::string translate(const std::string&)=0;
Тогда объектов этого класса существовать не может.
Translator* t = new TranslatorRuEn();
text = t->translate("...");
delete t;
Что происходит при его выполнении? В первой строчке отводится место и вызывается конструктор. Во второй - вызов виртуальной функции. В третьей же – вызов деструктора. Но при компиляции неизвестно, какой именно деструктор вызывать, так что вызван будет деструктор класса Translator, что, скорее всего, не то, что нужно. Итак, при использовании виртуальных функций деструктор тоже должен быть виртуальным.
virtual ~Translator();