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

Max*_*Max 2 c++ abstract-class arguments vector c++17

我试图使用抽象/接口类的向量Base作为函数的参数g(在main.cpp),以便我可以将派生类的向量作为参数传递。

\n

由于我无法拥有 的实例Base,因此我尝试传递唯一指针的向量 - 因为这似乎是执行此操作的首选方法。但是,由于某种原因,我不被允许从 转换vector<unique_ptr<Test::A>>vector<unique_ptr<Test::Base>>,我不明白为什么。

\n

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

\n

module.hpp看起来像这样:

\n
#ifndef _MODULE_HPP\n#define _MODULE_HPP\n\nnamespace Test\n{\n    class Base\n    {\n    public:\n        virtual ~Base(){};\n\n        virtual int f(int a, int b) = 0;\n    };\n\n    class A : public Base\n    {\n    public:\n        A();\n        ~A() = default;\n\n        int f(int a, int b);\n    };\n}\n\n#endif\n
Run Code Online (Sandbox Code Playgroud)\n

module.cpp看起来像这样:

\n
#include <module/module.hpp>\n\nTest::A::A() {}\n\nint Test::A::f(int a, int b)\n{\n    return a + b;\n};\n
Run Code Online (Sandbox Code Playgroud)\n

main.cpp看起来像这样:

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

当我尝试编译代码时,出现以下错误:

\n
g++ -std=c++17 -Wall -Wextra  -I src -I include -c -o build/module/module.o src/module/module.cpp\ng++ -std=c++17 -Wall -Wextra  -I src -I include -c -o build/main.o src/main.cpp\nsrc/main.cpp: In function \xe2\x80\x98int main(int, char**)\xe2\x80\x99:\nsrc/main.cpp:26:4: error: could not convert \xe2\x80\x98v\xe2\x80\x99 from \xe2\x80\x98vector<unique_ptr<Test::A>>\xe2\x80\x99 to \xe2\x80\x98vector<unique_ptr<Test::Base>>\xe2\x80\x99\n   26 |  g(v);\n      |    ^\n      |    |\n      |    vector<unique_ptr<Test::A>>\nmake: *** [Makefile:36: build/main.o] Error 1\n
Run Code Online (Sandbox Code Playgroud)\n

Jan*_*tke 5

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

多态存储

如果您需要多态存储,为什么不std::vector<std::unique_ptr<Base>>到处使用呢?你可以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);
}
Run Code Online (Sandbox Code Playgroud)

接受协变类型的向量

或者,如果您只需要存储 astd::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>>
{ ... }
Run Code Online (Sandbox Code Playgroud)

要调用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())
Run Code Online (Sandbox Code Playgroud)

注意:您也可以不限制模板,这将使在 C++17 中更容易实现。

  • @Max C++ 有这个,还有更多:D [`std::衍生自`](https://en.cppreference.com/w/cpp/concepts/衍生_from) (2认同)