#c #polymorphism #unique-ptr
#c #полиморфизм #уникальный-ptr
Вопрос:
Я хотел бы иметь член класса unique_ptr, который указывает на базовый класс, но позже в конструкторе с помощью полиморфизма может быть изменен, чтобы указывать на дочерний класс, который также является производным от того же базового класса.
Хотя я не получаю никаких ошибок в конструкторе, устанавливающем этот полиморфизм, похоже, он работает некорректно, поскольку я получаю сообщения об ошибках, что мой полиморфный указатель не может найти член сестринского класса, на который, как я думал, теперь указывает указатель.
Как мне правильно достичь полиморфизма здесь?
class A {
int bar;
};
class B : public A {
int foo;
};
class C: public A {
C();
std::unique_ptr<A> _ptr; // changing to std::unique_ptr<B> _ptr removes the "class A has no member 'foo'" error
};
C::C() : A()
{
_ptr = std::make_unique<B>(); // no errors here
int w = _ptr->foo; // class A has no member 'foo'
}
Комментарии:
1. Что именно вас здесь удивляет?
_ptr
указывает наA
. У классаA
нет членаfoo
.2. Здесь нет полиморфизма, только наследование.
3. И у вас есть UB, поскольку вы уничтожаете B как A без виртуального деструктора.
4. Если вам просто нужно использовать элемент foo здесь, вам потребуется некоторое приведение.
Ответ №1:
Когда вы назначаете
_ptr = std::make_unique<B>();
Это работает, потому что B
является производным классом A
, однако _ptr
все еще является unique_ptr
для базового класса. Вы не можете изменить тип переменной после ее объявления.
Итак, какие у вас варианты?
Поскольку вы знаете, что он _ptr
хранит указатель на производный класс B
, вы можете выполнить приведение после его разыменования:
_ptr = std::make_unique<B>();
// derefence the pointer, and cast the reference to `Bamp;`.
Bamp; reference_to_sister = (Bamp;)(*_ptr);
int w = reference_to_sister.foo;
Если вы воспользуетесь этим подходом, вам придется каким-то образом отслеживать, в каком производном классе находится _ptr
, иначе вы рискуете столкнуться с ошибками.
В качестве альтернативы, если вы используете C 17, вы можете использовать std::variant
:
class C : public A {
void initialize(Aamp; a) {
// Do stuff if it's the base class
}
void initialize(Bamp; b) {
// Do different stuff if it's derived
int w = b.foo;
}
C() {
_ptr = std::make_unique<B>(); // This works
// This takes the pointer, and calls 'initialize'
auto initialize_func = [amp;](autoamp; ptr) { initialize(*ptr); };
// This will call 'initialize(Aamp;)' if it contains A,
// and it'll call 'initialize(Bamp;)' if it contains B
std::visit(initialize_func, _ptr);
}
std::variant<std::unique_ptr<A>, std::unique_ptr<B>> _ptr;
};
На самом деле, если вы используете std::variant
это будет работать, даже если A
и B
являются совершенно не связанными классами.
Вот еще один короткий variant
пример
#include <variant>
#include <string>
#include <iostream>
void print(std::stringamp; s) {
std::cout << "String: " << s << 'n';
}
void print(int i) {
std::cout << "Int: " << i << 'n';
}
void print_either(std::variant<std::string, int>amp; v) {
// This calls `print(std::stringamp;) if v contained a string
// And it calls `print(int)` if v contained an int
std::visit([](autoamp; val) { print(val); }, v);
}
int main() {
// v is empty right now
std::variant<std::string, int> v;
// Put a string in v:
v = std::string("Hello, world");
print_either(v); //Prints "String: Hello, world"
// Put an int in v:
v = 13;
print_either(v); //Prints "Int: 13"
}
Комментарии:
1. Ах, спасибо! Я путал полиморфизм с приведением.
2. Конечно! Дайте мне знать, если у вас есть какие-либо другие вопросы или проблемы