C++ 根据选项更改父 class

C++ change parent class based on option

有一个 Student class 继承自 Person。 还有 Student class 继承自 University。 我想根据选项改变parent class Person, University而不重写Student比如Student1Student2(因为studentclass很复杂)。 这是示例代码。

class Person {
  void f() {printf("I'm person")}
};

class University {
  void f() {printf("I'm university")}
};

class Student1 : public Person {
  void g() {f()}
};
class Student2 : public University {
  void g() {f()} // I don't wan't to rewrite this!
};

if (option.person) {
  Student1 student;
}
else {
  Student2 student;
}

由于我们无法知道 compile-time 中的 option.person 是什么,我们需要找到一种在运行时解决该问题的方法。

这样做的一个选择是std::variant,它可以存储任意数量的不同类型;但这样做的代价是始终具有与最大模板类型相同的大小。

例如,如果我这样做:

std::variant<char, int> myVariant = '!';

即使 myVariant 包含 char(1 字节),它也使用 4 字节的 RAM,因为 int 是 4 字节。

使用变体

我们可以将 'base' 类型作为 Student 中的变量来维护,而不是从不在 compile-time 处共享公共基础的不同对象继承。

#include <iostream>
#include <variant>
#include <concepts>

class Person {
public:
    void f()
    {
        std::cout << "I'm a person!\n";
    }
};
class University {
public:
    void f()
    {
        std::cout << "I'm a university!\n";
    }
};

class Student {
public:
    using variant_t = std::variant<Person, University>;
    variant_t base;

    // Here we accept an rvalue of any type, then we move it to the 'base' variable.
    //  if the type is not a Person or University, a compiler error is thrown.
    Student(auto&& owner) : base{ std::move(owner) } {}

    void g()
    {
        // METHOD 1:  Using std::holds_alternative & std::get
        //             This has the advantage of being the simplest & easiest to understand.
        if (std::holds_alternative<Person>(base))
            std::get<Person>(base).f();
        else if (std::holds_alternative<University>(base))
            std::get<University>(base).f();


        // METHOD 2:  Using std::get_if
        //             This has the advantage of being the shortest.
        if (auto* person = std::get_if<Person>(&base))
            person->f();
        else if (auto* university = std::get_if<University>(&base))
            university->f();


        // METHOD 3:  Using std::visit
        //             This has the advantage of throwing a meaningful compiler error if-
        //             -we modify `variant_t` and end up passing an unhandled type.
        std::visit([](auto&& owner) {
            using T = std::decay_t<decltype(owner)>;
            if constexpr (std::same_as<T, Person>)
                owner.f(); //< this calls  `Person::f()`
            else if constexpr (std::same_as<T, University>)
                owner.f(); //< this calls  `University::f()`
            else static_assert(false, "Not all potential variant types are handled!");
        }, base);
    }
};

在这个例子中,我展示了 3 种不同的方法来访问 base 的基础值。
结果,输出为:

进一步阅读: