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>, ...>
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>>
当您希望它们按值在同一向量中时应该使用。动态多态对象必须具有完全相同的基类,即使这样您也需要通过引用而不是值将它们放入容器中。
TA贡献1847条经验 获得超11个赞
如果你创建一个非模板化的空基类,一个从它继承的模板,然后是特化,你可以将基指针存储在一个向量或任何其他结构中。如果你想使用存储的对象,你必须明确地将它们转换回实际类型(如果我是正确的话,这也必须在 Java 中完成)。
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::any
, std::variant
)。
添加回答
举报