代码之家  ›  专栏  ›  技术社区  ›  Max

如何使用抽象/接口类的向量作为函数参数?

  •  0
  • Max  · 技术社区  · 3 年前

    我正在尝试使用一个抽象/接口类的向量 Base 作为函数的参数 g (英寸 main.cpp ),这样我就可以将派生类的向量作为参数传递。

    因为我不能有 基础 ,我尝试传递唯一指针的矢量,因为这似乎是首选的方法 vector<unique_ptr<Test::A>> vector<unique_ptr<Test::Base>> 出于某种原因,我不明白为什么。

    问题:为什么这是不允许的,我该如何解决?

    module.hpp 看起来是这样的:

    #ifndef _MODULE_HPP
    #define _MODULE_HPP
    
    namespace Test
    {
        class Base
        {
        public:
            virtual ~Base(){};
    
            virtual int f(int a, int b) = 0;
        };
    
        class A : public Base
        {
        public:
            A();
            ~A() = default;
    
            int f(int a, int b);
        };
    }
    
    #endif
    

    module.cpp 看起来是这样的:

    #include <module/module.hpp>
    
    Test::A::A() {}
    
    int Test::A::f(int a, int b)
    {
        return a + b;
    };
    

    main.cpp 看起来是这样的:

    #include <module/module.hpp>
    
    #include <iostream>
    #include <vector>
    #include <memory>
    
    void g(std::vector<std::unique_ptr<Test::Base>> v)
    {
        for (std::unique_ptr<Test::Base> &vi : v)
        {
            std::cout << vi->f(1, 2) << " ";
        }
        std::cout << std::endl;
    }
    
    int main(int argc, char **argv)
    {
        (void)argc;
        (void)argv;
    
        std::vector<std::unique_ptr<Test::A>> v;
    
        v.emplace_back(std::make_unique<Test::A>());
        v.emplace_back(std::make_unique<Test::A>());
    
        g(v);
    
        return 0;
    }
    

    当我试图编译代码时,我会得到以下错误:

    g++ -std=c++17 -Wall -Wextra  -I src -I include -c -o build/module/module.o src/module/module.cpp
    g++ -std=c++17 -Wall -Wextra  -I src -I include -c -o build/main.o src/main.cpp
    src/main.cpp: In function ‘int main(int, char**)’:
    src/main.cpp:26:4: error: could not convert ‘v’ from ‘vector<unique_ptr<Test::A>>’ to ‘vector<unique_ptr<Test::Base>>’
       26 |  g(v);
          |    ^
          |    |
          |    vector<unique_ptr<Test::A>>
    make: *** [Makefile:36: build/main.o] Error 1
    
    0 回复  |  直到 3 年前
        1
  •  4
  •   Jan Schultke    3 年前

    std::vector<std::unique_ptr<Base>> std::vector<std::unique_ptr<Derived>> 不是 covariant ,并且它们之间不存在转换函数,因此不能简单地将其中一个视为另一个。 它们是完全不同的、不相关的类型。

    多态存储

    如果您需要多态存储,为什么不直接使用 std::vector<std::unique_ptr<基础>> 处处 您可以插入 std::unique_ptr<Derived> 像这样:

    // note: passing by reference to avoid having to move the vector
    //       (copying would be illegal)
    void g(const std::vector<std::unique_ptr<Test::Base>> &v)
    {
        for (const std::unique_ptr<Test::Base> &vi : v)
        {
            std::cout << vi->f(1, 2) << " ";
        }
        std::cout << std::endl;
    }
    
    int main()
    {
        std::vector<std::unique_ptr<Test::Base>> v;
    
        v.emplace_back(std::make_unique<Test::A>());
        v.emplace_back(std::make_unique<Test::A>());
    
        g(v);
    }
    

    接受协方差类型的向量

    或者,如果您只需要存储 std::vector<Derived> 但希望将其传递到函数中 Base 值,您可以执行以下操作:

    #include <concepts>
    
    // C++20 version using concepts
    template <std::derived_from<Test::Base> Derived>
    void g(std::vector<Derived> &v)
    {
        for (Test::Base &vi : v)
        {
            std::cout << vi.f(1, 2) << " ";
        }
        std::cout << std::endl;
    }
    
    // C++17 counterpart
    template <typename Derived>
    auto g(std::vector<Derived> &v)
        -> std::enable_if_t<std::is_base_of_v<Test::Base, Derived>>
    { ... }
    

    呼叫 g ,我们需要申报 std::vector<Test::A> v; 在里面 main 。 如果我们使用范围,我们可以使这个函数更加通用,这样我们就不再依赖 std::vector 明确地

    // C++20 version
    template <std::ranges::input_range Range>
    requires (std::derived_from<std::ranges::range_value_t<Range>, Test::Base>)
    void g(Range &v)
    {
        for (Test::Base &vi : v)
        {
            std::cout << vi.f(1, 2) << " ";
        }
        std::cout << std::endl;
    }
    
    // C++17 version would be much more difficult because we lack the proper type traits.
    // If we accepted two iterators, it would become easier.
    template <typename ForwardIt>
    auto g(ForwardIt begin, ForwardIt end)
        -> std::enable_if_t<std::is_base_of_v<Test::Base, typename std::iterator_traits<ForwardIt>::value_type>>
    { ... }
    
    // call this function like g(v.begin(), v.end())
    

    注意:您也可以让模板不受约束,这将使其更容易在C++17中实现。