Как я могу получить полиморфное поведение в конструкторе C++?
-
12-09-2019 - |
Вопрос
У меня есть базовый класс, который я хочу выглядеть так:
class B
{
// should look like: int I() { return someConst; }
virtual int I() = 0;
public B() { something(I()); }
}
Дело в том, чтобы заставить производные классы переопределить I
и заставить его вызываться при создании каждого объекта.Это используется для ведения некоторой бухгалтерии, и мне нужно знать, какой тип объекта создается (но в противном случае я рассматриваю текущий объект как базовый класс).
Это не работает, поскольку C++ не позволяет вызывать абстрактную виртуальную функцию из конструктора.
Есть ли способ получить тот же эффект?
На основе эта ссылка казалось бы, ответ таков: нет способа получить то, что я хочу.Однако там написано:
Краткий ответ:нет.Базовый класс ничего не знает о том, от какого класса он произошел, и это тоже хорошо.
[...]
То есть объект официально не становится экземпляром Derived1, пока не запустится конструктор Derived1::Derived1.
Однако в моем случае я не хочу знать, что это такое. является но что это будет становиться.На самом деле, меня даже не волнует, что я получу в ответ, если я, пользователь, могу (постфактум) сопоставить это с классом.Так что я мог бы даже использовать что-то вроде указателя возврата, и это сошло бы мне с рук.
(теперь вернемся к чтению этой ссылки)
Решение
Вы не можете вызывать виртуальные методы из конструктора (точнее, вы может вызовите их, но в конечном итоге вы вызовете функцию-член из конструируемого в данный момент класса). Проблема в том, что производный объект в этот момент еще не существует.С этим мало что можно поделать: полиморфный вызов виртуальных методов из конструктора просто исключен.
Вам следует переосмыслить свой дизайн - например, передать константу в качестве аргумента конструктору.
class B
{
public:
explicit B(int i)
{
something(i);
}
};
Видеть Часто задаваемые вопросы по C++ для большего.Если вы Действительно хотите вызывать виртуальные функции во время построения, прочитай это.
Другие советы
Возможно, использовать статический фабричный метод для каждого производного типа?Это обычный способ создания экзотических объектов (читай:те, у кого очень специфические требования к инициализации) в .NET, что я оценил.
class Base
{
protected Base(int i)
{
// do stuff with i
}
}
class Derived : public Base
{
private Derived(int i)
: Base(i)
{
}
public Derived Create()
{
return new Derived(someConstantForThisDerivedType);
}
}
Вызов виртуальных методов в базовых конструкторах обычно не одобряется, поскольку вы никогда не можете быть уверены в поведении конкретного метода, и (как уже отмечал кто-то другой) производные конструкторы еще не были вызваны.
Это не сработает, поскольку производный класс еще не существует на момент выполнения конструктора базового класса:
class Base
{
public:
Base()
{
// Will call Base::I and not Derived::I because
// Derived does not yet exist.
something(I());
}
virtual ~Base() = 0
{
}
virtual int I() const = 0;
};
class Derived : public Base
{
public:
Derived()
: Base()
{
}
virtual ~Derived()
{
}
virtual int I() const
{
return 42;
}
};
Вместо этого вы можете добавить аргументы в конструктор базового класса:
class Base
{
public:
explicit Base(int i)
{
something(i);
}
virtual ~Base() = 0
{
}
};
class Derived : public Base
{
public:
Derived()
: Base(42)
{
}
virtual ~Derived()
{
}
};
Или, если вам действительно нравится ООП, вы также можете создать пару дополнительных классов:
class Base
{
public:
class BaseConstructorArgs
{
public:
virtual ~BaseConstructorArgs() = 0
{
}
virtual int I() const = 0;
};
explicit Base(const BaseConstructorArgs& args)
{
something(args.I());
}
virtual ~Base() = 0
{
}
};
class Derived : public Base
{
public:
class DerivedConstructorArgs : public BaseConstructorArgs
{
public:
virtual ~DerivedConstructorArgs()
{
}
virtual int I() const
{
return 42;
}
};
Derived()
: Base(DerivedConstructorArgs())
{
}
virtual ~Derived()
{
}
};
Что вам нужно, это двухэтапное строительство.Используйте лекарство универсального программиста:Добавьте еще один уровень косвенности.