В настоящее время я читаю о классах миксина и думаю, что понимаю все более или менее. Единственное, чего я не понимаю, это почему мне больше не нужны виртуальные функции. (Увидеть Вот а также Вот)
Например. великий волк пишет в его ответ здесь что виртуальные функции не нужны. Вот пример: (Я просто скопировал основные части)
struct Number
{
typedef int value_type;
int n;
void set(int v) { n = v; }
int get() const { return n; }
};
template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE
{
typedef T value_type;
T before;
void set(T v) { before = BASE::get(); BASE::set(v); }
void undo() { BASE::set(before); }
};
typedef Undoable<Number> UndoableNumber;
int main()
{
UndoableNumber mynum;
mynum.set(42); mynum.set(84);
cout << mynum.get() << '\n'; // 84
mynum.undo();
cout << mynum.get() << '\n'; // 42
}
Но что теперь будет, если я сделаю что-то вроде этого:
void foo(Number *n)
{
n->set(84); //Which function is called here?
}
int main()
{
UndoableNumber mynum;
mynum.set(42);
foo(&mynum);
mynum.undo();
cout << mynum.get() << '\n'; // 42 ???
}
Какое значение имеет mynum и почему? Работает ли полиморфизм в foo()
?!?
n-> набор (84); // Какая функция здесь вызывается?
Number::set
будет называться здесь.
Работает ли полиморфизм в foo ()?!?
Нет, без virtual
, Если вы попробуете код, вы получите неопределенное значение, потому что before
не устанавливается вообще.
Я скомпилировал ваш код в VS 2013, и он дает неуказанное число.
У вас нет конструктора в вашей структуре, что означает, что переменная before не инициализирована.
Ваш пример кода вызывает неопределенное поведение, потому что вы пытаетесь читать из int
переменная n
пока он не находится в действительном состоянии. Вопрос не в том, какое значение будет напечатано. Ваша программа не обязана печатать что-либо или делать что-либо, что имеет смысл, хотя вы, вероятно, используете машину, на которой неопределенное поведение будет отображаться как случайное значение n
или на котором он в основном будет отображаться как 0.
Ваш компилятор, вероятно, даст вам важный совет, если вы позволите ему обнаруживать такие проблемы, например:
34:21: warning: 'mynum.Number::n' is used uninitialized in this function [-Wuninitialized]
Однако неопределенное поведение начинается еще до этого. Вот как это происходит, шаг за шагом:
UndoableNumber mynum;
Это также создает Number
подобъект с unintialised n
. Тот n
имеет тип int
и, таким образом, его отдельные биты могут быть установлены на так называемые представление ловушки.
mynum.set(42);
Это вызывает производный класс set
функция. Внутри set
сделана попытка установить before
переменная-член для неинициализированного n
значение с возможным представлением ловушки:
void set(T v) { before = BASE::get(); BASE::set(v); }
Но вы не можете безопасно сделать это. before = BASE::get()
часть уже не права, потому что Base::get()
копирует int
с возможным представлением ловушки. Это уже неопределенное поведение.
Это означает, что с этого момента C ++ как язык программирования больше не определяет, что произойдет. Рассуждение об остальной части вашей программы является спорным.
Все же давайте предполагать на мгновение, что копия будет в порядке. Что еще будет потом?
Base::set
называется, настройка n
к действительному значению. before
остается в своем предыдущем недействительном статусе.
Сейчас foo
называется:
void foo(Number *n) { n->set(84); //Which function is called here? }
база-учебный класс set
называется потому что n
имеет тип Number*
а также set
является невиртуальном.
set
счастливо устанавливает n
переменная-член до 84. Производный класс before
остается недействительным.
Теперь undo
Функция вызывается и выполняет следующие действия:
BASE::set(before);
После этого назначения, n
больше не 84, но установлено недействительным before
значение.
И наконец…
cout << mynum.get() << '\n';
get
возвращает неверное значение. Вы пытаетесь распечатать его. Это даст неуказанные результаты даже на машине, у которой нет представления ловушек для int
s (вы, скорее всего, используете такую машину).
Заключение:
C ++ как язык не определяет, что делает ваша программа. Он может что-то печатать, ничего не печатать, аварийно завершать работу или делать то, что ему нравится, все потому, что вы копируете int
,
На практике, сбой или выполнение того, что он чувствует, маловероятно на типичном компьютере конечного пользователя, но все еще не определено, что будет напечатано.
Если вы хотите, чтобы ваш производный класс set
быть вызванным при вызове на Number*
тогда вы должны сделать set
virtual
функция в Number
,