Полиморфный член класса unique_ptr - PullRequest
0 голосов
/ 05 апреля 2019

Я хотел бы иметь член класса 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 Ответ

0 голосов
/ 05 апреля 2019

При назначении

_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 `B&`. 
B& reference_to_sister = (B&)(*_ptr);
int w = reference_to_sister.foo; 

Если вы воспользуетесь этим подходом, вам придется каким-то образом отслеживать, какой производный класс находится в _ptr, иначе вы рискуете столкнуться с ошибками.

В качестве альтернативы, если вы используете C ++ 17, вы можете использовать std::variant:

class C : public A {
  void initialize(A& a) {
      // Do stuff if it's the base class
  }
  void initialize(B& 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 = [&](auto& ptr) { initialize(*ptr); };
      // This will call 'initialize(A&)' if it contains A,
      // and it'll call 'initialize(B&)' 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::string& s) {
    std::cout << "String: " << s << '\n';
}
void print(int i) {
    std::cout << "Int: " << i << '\n'; 
}

void print_either(std::variant<std::string, int>& v) {
    // This calls `print(std::string&) if v contained a string
    // And it calls `print(int)` if v contained an int
    std::visit([](auto& 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"
}
...