【问题标题】:list of base class pointers, get different return values for function member基类指针列表,获取函数成员的不同返回值
【发布时间】:2021-05-01 16:51:25
【问题描述】:

想象以下情况

  • 你有一个指向变量的指针:fooVar *
  • 您可以使用如下函数获取变量的类型:get_type(fooVar* foo),它返回一个 int 值bar
  • 然后您可以在enum 中查找返回值bar 所属的类型,例如1 -> type_int
  • 然后根据类型,您可以调用以下函数之一来获取值:double get_double(fooVar*)int get_int(fooVar*)、...

目标是

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

问题是

  • 我想存储所有变量的列表。
  • 如何统一返回变量的当前值?

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

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

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

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

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

另一个解决方案可能涉及std::variant,但我认为这有点矫枉过正 - 对吧?

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


旁注:我在这里读到的关于 SO 的主题都不适合,这对我来说就像一个标准问题,所以我想学习一些专业程序员解决这类问题的方法。

【问题讨论】:

    标签: c++ pointers inheritance idioms


    【解决方案1】:

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

    因此,也许您可​​以做的最直接的事情是拥有一个 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' 等)

    【讨论】:

    • 谢谢你!我现在将尝试理解并实施它。然后我会回来选择接受的答案。再次感谢:)
    • 你能不能稍微扩展一下“但是这些天,我们尽量避免使用指针数组。”那么,一般来说,你更喜欢什么?
    • @ataraxis 一个对象数组,或者至少是一个 smart 指针数组(unique_ptrshared_ptr),而不是 raw 指针
    • @RemyLebeau:好点,我会添加那个选项。
    • @ataraxis:参见关于避免指针数组的编辑。
    【解决方案2】:

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

    在发生错误时引发异常的另一种方法是分别为getAsDouble()getAsBool()getAsInt() 返回std::optional&lt;double&gt;std::optional&lt;bool&gt;std::optional&lt;int&gt;。这样一来,返回一个空的 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
    

    您可以编写一个包含用户定义的转换运算符到FooVar *const FooVar * 的包装类:

    class FooVarWrapper {
       FooVar *ptr_;
    public:
       // ...
    
       operator FooVar*() noexcept { return ptr_; }
       operator const FooVar*() const noexcept { return ptr_; }
    
       // ...
    };
    

    请注意,由于转换运算符,您仍然可以将 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()
    

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

    【讨论】:

    • 这很复杂,等我看懂了没有xD还是谢谢你!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-24
    • 2018-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-12
    相关资源
    最近更新 更多