C++ 基类指针列表,为函数成员获取不同的返回值

C++ 基类指针列表,为函数成员获取不同的返回值,c++,pointers,inheritance,idioms,C++,Pointers,Inheritance,Idioms,想象一下下面的情况 您有一个指向变量fooVar的指针* 您可以使用如下函数获取变量的类型:get_typefooVar*foo,它返回一个int值条 然后,您可以在一个枚举中查找返回值栏所属的类型,例如1->type\u int 根据类型,然后可以调用以下函数之一来获取值:double get_doublefooVar*,int get_intfooVar*。。。 目标是 将这些函数(C库的简化版本)包装到CPP类中 问题是 我想存储所有变量的列表。 如何以统一的方式返回变量的当前值? 我尝试

想象一下下面的情况

您有一个指向变量fooVar的指针* 您可以使用如下函数获取变量的类型:get_typefooVar*foo,它返回一个int值条 然后,您可以在一个枚举中查找返回值栏所属的类型,例如1->type\u int 根据类型,然后可以调用以下函数之一来获取值:double get_doublefooVar*,int get_intfooVar*。。。 目标是

将这些函数(C库的简化版本)包装到CPP类中

问题是

我想存储所有变量的列表。 如何以统一的方式返回变量的当前值? 我尝试创建一个基类型并从中派生其他类型,如下所示:

BaseClass
|--> IntClass - int getValue()
|--> DoubleClass - double getValue()
'--> BoolClass - bool getValue()
然后,我可以为每个具有正确返回类型的派生类创建一个成员函数.getValue,如上所示。 因为我将指向所有变量的指针存储在一个类型为BaseClass*的列表中,所以我的编译器现在抱怨说,当我试图调用它时,getValue没有定义

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

第三种解决方案可能是不仅存储指针,还存储它指向的派生类的类型。后来我就可以投出BasePointer了。但这真的是个好主意吗

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

问题是:你如何在C++中做这件事?有没有一个干净的方法来实现这一点


旁注:我在这里读到的所有主题都不太合适,我觉得这是一个标准问题,因此我想学习专业程序员解决此类问题的一些方法。

似乎您已经陷入了尝试重新实现虚拟函数和动态继承的困境

因此,您可以做的最简单的事情可能是拥有一个虚拟BaseType::getValue函数,该函数由特定于子类的getValue重写

但是现在,我们试图避免原始指针数组——这需要显式分配和反分配,并且容易出错,例如——如果抛出异常会发生什么?。至少,让它像std::vector或std::shared\u ptr一样,而不是std::unique\u ptr。如果您没有听说过这些类似指针的类,请阅读以下内容:

除此之外,如果您事先知道可能的类型集,并且它并不庞大,那么,正如您自己所建议的那样,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

现在,您的_complex_访问有点像std::visit,只是您只查看arg的arg.getValue intead。或者您可以在上面使用std::visit,但需要将类型更改为外部类型IntClass,而不是“int”等。

您似乎已经陷入了尝试重新实现虚拟函数和动态继承的困境

因此,您可以做的最简单的事情可能是拥有一个虚拟BaseType::getValue函数,该函数由特定于子类的getValue重写

但是现在,我们试图避免原始指针数组——这需要显式分配和反分配,并且容易出错,例如——如果抛出异常会发生什么?。至少,让它像std::vector或std::shared\u ptr一样,而不是std::unique\u ptr。如果您没有听说过这些类似指针的类,请阅读以下内容:

除此之外,如果您事先知道可能的类型集,并且它并不庞大,那么,正如您自己所建议的那样,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
现在,您的_complex_访问有点像std::visit,只是您只查看arg的arg.getValue intead。或者您可以在上面使用std::visit,但需要将类型更改为外部类型IntClass,而不是“int”等

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

在发生错误时引发异常的另一种方法是分别为getAsDouble、getAsBool和getAsInt返回std::optional、std::optional和std::optional。这样,返回一个空的std::选项将发出一个故障信号——即没有这样的值可检索——这不是一个错误

例如,如果您具有与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
您可以编写一个包装器类,其中包含用户定义的FooVar*和cons转换运算符 t FooVar*:

请注意,由于使用了转换运算符,您仍然可以将C API直接用于此包装类,例如,您可以使用FooVarWrapper对象作为参数调用原始函数get_type,因为这将隐式转换为存储的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()

但是,您可能需要考虑将转换操作符标记为显式,以避免轻易绕过返回STD:可选的函数:直接调用GETION It或GETSUBLUE。这样,每当您想要将FooVarWrapper对象转换为存储的指针时,就需要编写静态_cast;转换不会隐式进行

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

在发生错误时引发异常的另一种方法是分别为getAsDouble、getAsBool和getAsInt返回std::optional、std::optional和std::optional。这样,返回一个空的std::选项将发出一个故障信号——即没有这样的值可检索——这不是一个错误

例如,如果您具有与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
您可以编写一个包装器类,其中包含用户定义的到FooVar*和const FooVar*的转换运算符:

请注意,由于使用了转换运算符,您仍然可以将C API直接用于此包装类,例如,您可以使用FooVarWrapper对象作为参数调用原始函数get_type,因为这将隐式转换为存储的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()

但是,您可能需要考虑将转换操作符标记为显式,以避免轻易绕过返回STD:可选的函数:直接调用GETION It或GETSUBLUE。这样,每当您想要将FooVarWrapper对象转换为存储的指针时,就需要编写静态_cast;转换不会隐式进行。

谢谢您!我现在将努力理解并实施它。然后我会回来选择接受的答案。再次感谢:你能不能再多说一点,但现在,我们尽量避免使用指针数组。一般来说,你更喜欢什么?@ataraxis是一个对象数组,或者至少是一个智能指针数组,唯一的或共享的,而不是原始的pointers@RemyLebeau:说得好,我将添加该选项。@ataraxis:请参阅编辑关于避免指针数组的内容。谢谢您!我现在将努力理解并实施它。然后我会回来选择接受的答案。再次感谢:你能不能再多说一点,但现在,我们尽量避免使用指针数组。一般来说,你更喜欢什么?@ataraxis是一个对象数组,或者至少是一个智能指针数组,唯一的或共享的,而不是原始的pointers@RemyLebeau:说得好,我将添加该选项。@ataraxis:请参阅编辑关于避免指针数组的内容。这很复杂,过一会儿让我看看我是否理解它xD,谢谢!这很复杂,过一会儿让我看看我是否理解了xD,谢谢!