多态unique_ptr类成员

Polymorphic unique_ptr class member

本文关键字:成员 ptr unique 多态      更新时间:2023-10-16

我希望有一个指向基类的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'
}

当您分配时

_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即使AB是完全不相关的类,这也将起作用。

这是另一个简短的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"
}