为了账号安全,请及时绑定邮箱和手机立即绑定

多态性的 C++ 模板?“使用类模板需要模板参数”

多态性的 C++ 模板?“使用类模板需要模板参数”

侃侃无极 2023-05-10 13:32:48
我是 C++ 的新手,正在尝试弄清楚如何使用模板来构建特定类型的类的层次结构以实现多态性。我知道如何使用泛型在 Java 中很容易地做到这一点。我确定之前有人问过这个问题,但我只是不知道在 C++ 中使用什么术语来搜索我想要的内容。我希望能够使用我创建的基类,该基类使用由下面定义的非常指定的类Derived_1和组成的对象组合创建Derived_2,每个类都从名为 的类扩展Base。但是,我收到一个编译器错误提示我需要声明类型?我使用什么语法来指示z在实现中使用的这个向量可以由从 扩展的类的任意组合组成Base?到目前为止,这是我所拥有的,我认为在声明基类和扩展该基类的类时大部分是正确的:base_types.htemplate <typename T>class Base {public:    Base<T>(size_t a, T b) :            m_a(a),            m_b(b) {    }    T getB() const;    size_t m_a;    T m_b;};// specific kind of Base that uses boolclass Derived_1 : public Base<bool> {    Derived_1(uint32_t a);     // second parameter is unused, is assumed to be "true"    bool getB();};// specific kind of Base that uses size_tclass Derived_2 : public Base<size_t> {    Derived_2(uint32_t a, size_t b);    size_t getB();};基础类型.cppDerived_1::Derived_1(uint32_t a) : Base(a, true) { }      // second parameter is unused, is assumed to be "true"bool Derived_1::getB() { return m_b; }Derived_2::Derived_2(uint32_t a, size_t b) : Base(a, b) { }bool Derived_2::getB() { return m_b; }impl_types.h#include "base_types.h"#include <vector>class Foo {public:    Foo(        size_t y,        const std::vector<Base>& z);      // Error: Use of class template 'Base' requires template arguments};impl_types.cppclass Foo {public:    Foo(        size_t y,        const std::vector<Base>& z) :      // Error: Use of class template 'Base' requires template arguments            m_y{y},            m_z{z};};作为参考,这是我想做的事情的 Java 实现:// Base.javaclass Base<T> {    int m_a;    T m_b;    Base(int a, T b) {        m_a = a;        m_b = b;    }    T getB() {        return m_b;    }}// Derived_1.javaclass Derived_1 extends Base<Boolean> {    Derived_1(int a, Boolean a) {        super(a, b);    }    Boolean getB() {        return m_b;    }}// Derived_2.javaclass Derived_2 extends Base<String> {    Derived_2(int a, String b) {        super(a, b);    }    String getB() {        return m_b;    }}
查看完整描述

4 回答

?
月关宝盒

TA贡献1772条经验 获得超5个赞

在 Java 中,这是可行的,因为 aArrayList<Widget>基本上是ArrayList<Object>. 这有一些优点,但也有缺点。


在 C++ 中,有模板而不是泛型。出于相似的目标,它们在实现和含义方面有很大不同。


正如他们的名字所说:他们是模板。类模板不是类。它是某种只存在于编译时的实体。只有当模板被实例化时,它才会变得具体。


ELI5关于实例化一个模板的过程:std::vector就是模板。std::vector<int>是模板的实例化。模板通过填充代码的漏洞来实例化,由模板参数表示:


template<typename T>

auto value() -> T { return T{}; }


int main() {

    return value<int>() + value<short>();

}

实例化了两个函数,看起来像这样:


template<> //   v---- T has been replaced!

auto value() -> int { return int{}; }


template<> // Another instantiation

auto value() -> short { return short{}; }

当然,编译器不会以文本方式替换它,而是使用 AST 来解析实际类型。


这是为了向您展示模板在该过程之后并不真正存在。只有实例化是具体的。并且有多个实例化。


就像上面的函数模板一样,当一个模板类被实例化时,一个全新的类型就被创建了。实例化创建的类型完全不相关。它们只是不同的类型,就像模板函数产生不同的实例一样。


那么...如果您有许多不同的、不相关的类,您如何实现多态性呢?


你加个接口!


struct Interface {

    // TODO: put useful function there

};


template<typename T>

struct Base : Interface {

    virtual auto getB() const -> T;

};


struct Impl1 : Base<bool> {

    auto getB() const -> T override;

};

如果相反,你的意图是做这样的事情:


Base b = ...;


// pseudocode

if (typeid(b->getB()) == bool)

{

    bool b = dynamic_cast<bool>(b->getB());

}

else if (typeid(b->getB()) == std::size_t)

{

    std::size_t b = dynamic_cast<std::size_t>(b->getB());

}

然后,像其他答案一样,一个变体就是解决方案。Base如果你像这样列出可能的类型,那么你就提前知道了可能类型的列表。所以 astd::variant就是你想要的。


为了使变体更易于使用,您始终可以为类型添加别名:


using VBase = std::variant<Base<bool>, Base<std::size_t>, ...>

查看完整回答
反对 回复 2023-05-10
?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

你不能std::vector<Base>因为Base是模板。你应该有std::vector<Base<bool>>std::vector<Base<size_t>> 或者类似的。Base<bool>并且Base<size_t>是不同的类型,除了使用相同的模板制作之外没有其他共同之处Base

可能是您想要的,std::vector<std::variant<Base<bool>,Base<size_t>>>但很难从您的代码中分辨出来。用于std::variant具有可以具有不同类型值的变量(可能没有共同点)。

您不能将派生类的对象放入基类的向量中,因此std::vector<std::variant<Derived1,Derived2>>当您希望它们按值在同一向量中时应该使用。动态多态对象必须具有完全相同的基类,即使这样您也需要通过引用而不是值将它们放入容器中。


查看完整回答
反对 回复 2023-05-10
?
回首忆惘然

TA贡献1847条经验 获得超11个赞

如果你创建一个非模板化的空基类,一个从它继承的模板,然后是特化,你可以将基指针存储在一个向量或任何其他结构中。如果你想使用存储的对象,你必须明确地将它们转换回实际类型(如果我是正确的话,这也必须在 Java 中完成)。



查看完整回答
反对 回复 2023-05-10
?
慕盖茨4494581

TA贡献1850条经验 获得超11个赞

错误:使用类模板Base需要模板参数

你得到错误是因为它Base不是一个类型,而只是一个模板。不要将 C++ 模板与 Java 泛型混淆,它们实际上是非常不同的概念。您不能拥有模板向量,因为模板只是模板。您需要实例化它们以获得类型。例如,您可以有一个std::vector<Base<bool>>.

您的代码中的另一个问题是Base应该有一个虚拟析构函数。否则你有内存泄漏的危险。必须声明方法以virtual启用动态分派。

话虽如此...

你真正想做什么:

我只是想持有一个从公共基类继承的不同类对象的向量,[...]

你不需要模板。这很简单:

#include <iostream>

#include <vector>

#include <memory>

#include <utility>

struct base { 

    virtual void some_method(){ std::cout << "base\n";}

    virtual ~base(){}

};

struct foo : base {

    virtual void some_method() override { std::cout << "foo\n";}

};

struct bar : base {

    virtual void some_method() override { std::cout << "bar\n";}

};



int main() {

    std::vector<std::shared_ptr<base>> v;

    v.emplace_back(new foo());

    v.emplace_back(new bar());


    for (auto& e : v) e->some_method();

    return 0;

}

多态性适用于指针或引用。由于在容器中存储引用并不是那么简单,所以我使用了指针。我使用智能指针,因为我不想在手动内存管理上乱来。


到目前为止,一切都很好...


[...] 但是使用在那些返回不同类型的类之间共享的方法,具体取决于该特定类


然而,这并不容易。首先,注意同一个方法不能有不同的返回类型。举个例子,如果你有


struct example_base { 

    virtual int foo() { return 1;} 

};

struct example_derived {

    virtual double foo() override { return 1.4; }

};

然后example_derived::foo不覆盖!_ example_base::foo感谢override编译器会通过一条错误消息告诉你


prog.cc:20:24: error: 'foo' marked 'override' but does not override any member functions

        virtual double foo() override { return 1.4; }

                       ^

根据您实际想要实现的目标(为什么您需要派生以不同的返回类型“共享一个通用方法”?),有不同的方法可以解决这个问题。我会告诉你一种方法。


#include <iostream>

#include <vector>

#include <memory>

#include <utility>


struct return_type_base { 

    virtual void print() {}

    virtual ~return_type_base() {}

};


struct bool_return_type : return_type_base {

    bool value = true;

    virtual void print() { std::cout << value << "\n"; }

};

struct int_return_type : return_type_base {

    int value = 3;

    virtual void print() { std::cout << value << "\n"; }

};


using return_type_ptr = std::shared_ptr<return_type_base>;


struct base { 

    virtual return_type_ptr some_method() = 0;

    virtual ~base(){}

};

struct foo : base {

    virtual return_type_ptr some_method() override { 

        return return_type_ptr(new bool_return_type());

    }

};

struct bar : base {

    virtual std::shared_ptr<return_type_base> some_method() override { 

        return return_type_ptr(new int_return_type());

    }

};



int main() {

    std::vector<std::shared_ptr<base>> v;

    v.emplace_back(new foo());

    v.emplace_back(new bar());


    for (auto& e : v) e->some_method()->print();

    return 0;

}

它基本上与上面的方法完全相同。为了以多态方式处理不同的类型,我们声明了一个公共基类并处理该基类的共享指针。如前所述,这只是一种可能的方法。请对它持保留态度,它只是为了给你一个起点。主要缺点是:它是侵入性的(您必须为每个要返回的类型编写一个类)并且它使用虚函数(即运行时开销)。有更好的方法,但细节对您实际想要对这些不同的返回类型做什么很重要。为了进一步阅读,我建议您搜索“type erasure”。


长话短说


模板是一个纯粹的编译时概念。如果你想在运行时平等对待不同的类型,你需要某种形式的运行时类型擦除。可用于此的技术在 Java 和 C++ 中有很大不同。通过虚函数实现的运行时多态性适用于两者,并且可能是最容易理解的一种,尤其是当您来自 Java 时。您还应该看看标准库必须提供的内容 ( std::anystd::variant)。



查看完整回答
反对 回复 2023-05-10
  • 4 回答
  • 0 关注
  • 145 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信