c++ 如何使用抽象/接口类的vector作为函数参数?

r9f1avp5  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(179)

我尝试使用抽象/接口类Base的向量作为函数g(在main.cpp中)的参数,以便我可以将派生类的向量作为参数传递。
因为我不能有Base的示例,所以我尝试传递唯一指针的向量--因为这似乎是首选的方式。但是,由于某种原因,我不允许从vector<unique_ptr<Test::A>>转换为vector<unique_ptr<Test::Base>>,我不明白为什么。

问题:为什么不允许这样做?我该如何解决?

module.hpp看起来像这样:

  1. #ifndef _MODULE_HPP
  2. #define _MODULE_HPP
  3. namespace Test
  4. {
  5. class Base
  6. {
  7. public:
  8. virtual ~Base(){};
  9. virtual int f(int a, int b) = 0;
  10. };
  11. class A : public Base
  12. {
  13. public:
  14. A();
  15. ~A() = default;
  16. int f(int a, int b);
  17. };
  18. }
  19. #endif

module.cpp看起来像这样:

  1. #include <module/module.hpp>
  2. Test::A::A() {}
  3. int Test::A::f(int a, int b)
  4. {
  5. return a + b;
  6. };

main.cpp看起来像这样:

  1. #include <module/module.hpp>
  2. #include <iostream>
  3. #include <vector>
  4. #include <memory>
  5. void g(std::vector<std::unique_ptr<Test::Base>> v)
  6. {
  7. for (std::unique_ptr<Test::Base> &vi : v)
  8. {
  9. std::cout << vi->f(1, 2) << " ";
  10. }
  11. std::cout << std::endl;
  12. }
  13. int main(int argc, char **argv)
  14. {
  15. (void)argc;
  16. (void)argv;
  17. std::vector<std::unique_ptr<Test::A>> v;
  18. v.emplace_back(std::make_unique<Test::A>());
  19. v.emplace_back(std::make_unique<Test::A>());
  20. g(v);
  21. return 0;
  22. }

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

  1. g++ -std=c++17 -Wall -Wextra -I src -I include -c -o build/module/module.o src/module/module.cpp
  2. g++ -std=c++17 -Wall -Wextra -I src -I include -c -o build/main.o src/main.cpp
  3. src/main.cpp: In function int main(int, char**)’:
  4. src/main.cpp:26:4: error: could not convert v from vector<unique_ptr<Test::A>>’ to vector<unique_ptr<Test::Base>>’
  5. 26 | g(v);
  6. | ^
  7. | |
  8. | vector<unique_ptr<Test::A>>
  9. make: *** [Makefile:36: build/main.o] Error 1
pwuypxnk

pwuypxnk1#

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

多态存储

如果你需要多态存储,为什么不到处都使用std::vector<std::unique_ptr<Base>>呢?你可以像这样插入std::unique_ptr<Derived>

  1. // note: passing by reference to avoid having to move the vector
  2. // (copying would be illegal)
  3. void g(const std::vector<std::unique_ptr<Test::Base>> &v)
  4. {
  5. for (const std::unique_ptr<Test::Base> &vi : v)
  6. {
  7. std::cout << vi->f(1, 2) << " ";
  8. }
  9. std::cout << std::endl;
  10. }
  11. int main()
  12. {
  13. std::vector<std::unique_ptr<Test::Base>> v;
  14. v.emplace_back(std::make_unique<Test::A>());
  15. v.emplace_back(std::make_unique<Test::A>());
  16. g(v);
  17. }

接受协变类型向量

或者,如果你只需要存储一个std::vector<Derived>,但想将它传递到接受一系列Base值的函数中,你可以这样做:

  1. #include <concepts>
  2. // C++20 version using concepts
  3. template <std::derived_from<Test::Base> Derived>
  4. void g(std::vector<Derived> &v)
  5. {
  6. for (Test::Base &vi : v)
  7. {
  8. std::cout << vi.f(1, 2) << " ";
  9. }
  10. std::cout << std::endl;
  11. }
  12. // C++17 counterpart
  13. template <typename Derived>
  14. auto g(std::vector<Derived> &v)
  15. -> std::enable_if_t<std::is_base_of_v<Test::Base, Derived>>
  16. { ... }

要调用g,我们需要在main中声明std::vector<Test::A> v;。如果我们使用范围,我们可以使这个函数更加通用,所以我们不再特别依赖std::vector

  1. // C++20 version
  2. template <std::ranges::input_range Range>
  3. requires (std::derived_from<std::ranges::range_value_t<Range>, Test::Base>)
  4. void g(Range &v)
  5. {
  6. for (Test::Base &vi : v)
  7. {
  8. std::cout << vi.f(1, 2) << " ";
  9. }
  10. std::cout << std::endl;
  11. }
  12. // C++17 version would be much more difficult because we lack the proper type traits.
  13. // If we accepted two iterators, it would become easier.
  14. template <typename ForwardIt>
  15. auto g(ForwardIt begin, ForwardIt end)
  16. -> std::enable_if_t<std::is_base_of_v<Test::Base, typename std::iterator_traits<ForwardIt>::value_type>>
  17. { ... }
  18. // call this function like g(v.begin(), v.end())
  • 注意:您也可以不限制模板,这将使其更容易在C++17中实现。
展开查看全部

相关问题