C++ 根据选项更改父 class
C++ change parent class based on option
有一个 Student class 继承自 Person。
还有 Student class 继承自 University。
我想根据选项改变parent class Person, University而不重写Student比如Student1和Student2(因为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
的基础值。
结果,输出为:
进一步阅读:
有一个 Student class 继承自 Person。 还有 Student class 继承自 University。 我想根据选项改变parent class Person, University而不重写Student比如Student1和Student2(因为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
的基础值。
结果,输出为:
进一步阅读: