基本 class 指针列表,为函数成员获取不同的 return 值

list of base class pointers, get different return values for function member

想象一下下面的情况

目标是

将这些函数(这是 C 库的简化版本)包装到 CPP class。

问题是

我尝试创建一个 BaseType 并从中派生其他类型,例如:

BaseClass
|--> IntClass - int getValue()
|--> DoubleClass - double getValue()
'--> BoolClass - bool getValue()

然后我可以为每个派生的 class 创建一个成员函数 .getValue(),并使用正确的 return 类型(如上所示)。 由于我将指向所有变量的指针存储在类型 BaseClass* 的列表中,我的编译器现在抱怨说 getValue() 在我尝试调用它时未定义。

第二种解决方案可能是创建 double getAsDouble()bool getAsBool()int getAsInt(),然后在类型不可转换或不适合时抛出错误。但这也感觉不对。

第三种解决方案可能不仅要存储指针,还要存储它指向的类型(派生的 class)。稍后我可以转换 BasePointer。但这真的是个好主意吗?

另一个解决方案可能涉及 std::variant,但我认为这太过分了 - 对吧?

问题是:你会如何在 C++ 中做到这一点?有没有一种干净的方法可以实现这一目标?


旁注: None 我在这里阅读的主题非常适合,这对我来说是一个标准问题,所以我想学习一些专业程序员解决此类问题的方法。

看来您已经陷入了尝试重新实现虚函数和动态继承的困境。

因此,也许您可​​以做的最直接的事情就是拥有一个 virtual BaseType::getValue() 函数,该函数被特定于子类的 getValue().

覆盖

但是现在,我们尽量避免使用原始指针数组 - 这需要显式分配和取消分配,并且容易出错(例如 - 如果抛出异常会怎样?)。至少,让它类似于 std::vector<std::unique_ptr<BaseType>>(或 std::shared_ptr 而不是 std::unique_ptr)。如果您还没有听说过这些类似指针的东西 类,请阅读:

What is a smart pointer and when should I use one?

除此之外 - 如果您事先知道可能的类型集,并且它不是很大 - 那么,正如您自己建议的那样 - std::variant 可能是一个合理的选择。 C++ 中的变体有点笨拙,但它们非常安全;一个你习惯了他们,他们很方便。所以,在你的情况下:

using foo_var = std::variant<IntClass, DoubleClass, BoolClass>;

//...

auto foo_vars = get_array_of_foo_vars_somehow();

for(const foo_var& : foo_vars) {
    your_complex_visit([]auto&& arg) {
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<T, int>)
            std::cout << "int with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, double>)
            std::cout << "double with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, bool>)
            std::cout << "bool with value " << arg << '\n';
        else 
            static_assert(always_false_v<T>, "non-exhaustive visitor!");
    }, foo_var);
}

// Note: No need to `free()` anything when the array goes out of scope

现在,your_complex_visit() 有点像 std::visit(),只是您看到的是 arg.getValue() 而不是 args()。或者你 可以 在上面使用 std::visit,但需要将类型更改为外部类型(IntClass 而不是 'int' 等)

A second solution might be to create double getAsDouble(), bool getAsBool(), int getAsInt() and then throw an error if the type is not convertible or if it doesn't fit. But that feels wrong, too.

在出现错误时抛出异常的替代方法可能是 return std::optional<double>std::optional<bool>std::optional<int> for getAsDouble()getAsBool()getAsInt()。这样,return空的 std::option 将发出失败信号——即没有这样的值可检索——这不是错误。

例如,如果您有以下对应于 C 代码的声明:

struct FooVar;

int get_type(const FooVar*);

int get_int(const FooVar*);
double get_double(const FooVar*);

#define INT_TYPE    1 
#define DOUBLE_TYPE 2

您可以编写一个包装器 class,其中包含到 FooVar *const FooVar *:

的用户定义转换运算符
class FooVarWrapper {
   FooVar *ptr_;
public:
   // ...

   operator FooVar*() noexcept { return ptr_; }
   operator const FooVar*() const noexcept { return ptr_; }

   // ...
};

请注意,由于转换运算符,您仍然可以直接将 C API 与此包装器 class 一起使用,例如,您可以使用 FooVarWrapper 对象作为参数,因为这将被隐式转换为存储的 FooVar *.

然后,您也可以将那些getter函数定义为非成员函数:

std::optional<int> getAsInt(const FooVarWrapper& obj) {
   if (INT_TYPE != get_type(obj))
      return std::nullopt;

   return get_int(obj);
}

std::optional<double> getAsDouble(const FooVarWrapper& obj) {
   if (DOUBLE_TYPE != get_type(obj))
      return std::nullopt;
   
   return get_double(obj);
}

// ... similarly for getAsBool()

但是,您可能需要考虑将转换运算符标记为 explicit,以避免通过调用 get_int() 或 [=31] 轻松绕过 return std::optional 的这些函数=] 直接。这样,每当您想将 FooVarWrapper 对象转换为存储指针时,都需要编写 static_cast<> ;转换不会隐式发生。