请解释为啥在 Cpp、Java 和 C# 中允许或不允许 List<A> 强制转换为 List<A.super> [关闭]
Posted
技术标签:
【中文标题】请解释为啥在 Cpp、Java 和 C# 中允许或不允许 List<A> 强制转换为 List<A.super> [关闭]【英文标题】:Please explain why List<A> cast to List<A.super> allowed or not allowed in Cpp, Java and C# [closed]请解释为什么在 Cpp、Java 和 C# 中允许或不允许 List<A> 强制转换为 List<A.super> [关闭] 【发布时间】:2016-08-29 10:33:13 【问题描述】:我发现:Cpp、Java 和 C# 对从 List of A 到 List of A.super 的转换有不同的控制策略。我知道这三种语言有不同的方式来实现泛型。 Cpp使用STL,Java使用擦除,CLR的实现更好。但是,我不知道为什么有些演员是允许的,而有些演员是不允许的。例如:
1.Cpp
class A
protected:
const char*_v;
public:
A(const char* v)
_v = v;
public:
void getValue()
std::cout << "A" << _v << std::endl;
;
class B : public A
public:
B(const char* v) :A(v)
public:
void getValue()
std::cout << "B" << _v << std::endl;
;
int _tmain(int argc, _TCHAR* argv[])
std::list<B> b;
B b01("01");
B b02("02");
b.push_back(b01);
b.push_back(b02);
//no
//std::list<A> a = static_cast <std::list<A> &>b;
//no
//std::list<A> a = dynamic_cast <std::list <A> & >b;
//yes
std::list<A> a = reinterpret_cast <std::list <A> &> (b);
a.front().getValue();
return 0;
2.Java
public static void testJava()
List<String> s = new ArrayList<String>();
s.add("01");
s.add("02");
//no
//List<Object> o = (List<Object>)s;
//yes
//List<Object> o = (List<Object>)(Object)s;
//yes
List<Object> o = (List<Object>)(List)s;
System.out.println(o.get(0));
3.C#
private static void testCS()
List<String> s = new List<String>()
"01",
"02"
;
//no
//List<Object> o = (List<Object>)s;
//no
//List<Object> o = (List<Object>)(Object)s;
//yes, but I think this is a new List
List<Object> o = s.OfType<Object>().ToList();
Console.WriteLine(o[0]);
你能给我解释一下吗?
谢谢!
【问题讨论】:
您不能在 Java 中将List<String>
转换为 List<Object>
,因为您可以将 Object
添加到 List<Object>
,但不能添加到 List<String>
。
因为 Java 和 C# 不允许您以这种方式射击自己的脚,但 C++ 允许您制作尽可能多的脚副本,并同时在所有脚上射击...
这是 3 个不同的问题。分开回答会更容易
@talex 我认为将它们标记为 3 个单独的重复项会更容易:)
@neohope 因为List
是原始类型,编译器不再知道元素类型的限制;它可能转换为List<Object>
是安全的,因此它允许强制转换,只是带有警告。
【参考方案1】:
这里发生的情况是,C# 和 Java 可以防止你犯下严重的错误,但 C++ 允许你这样做(因为语言的设计方式)。
这里有一些示例 C++ 代码演示了可能出现的问题。它与您的示例代码非常相似,只是稍作调整:在将list<B>
转换为list<A>
之后,它会将A
类型的元素推到列表中。然后它会尝试通过现有的list<B>
访问该元素,并获得可预测的结果(即它会在你的脸上爆炸):
#include "stdafx.h"
class A
protected:
const char*_v;
public:
A(const char* v)
_v = v;
public:
void getValue()
std::cout << "A" << _v << std::endl;
;
class B : public A
public:
B(const char* v) :A(v)
public:
void getValue()
std::cout << "B" << _v << std::endl;
void thisIsOnlyInB()
std::cout << "Only in B" << _v << std::endl;
;
int main()
std::list<B> b;
B b01("01");
b.push_back(b01);
std::list<A> a = reinterpret_cast <std::list <A> &> (b);
A a01("01");
a.push_back(a01);
b.front().thisIsOnlyInB(); // OK - item is really a B.
b.pop_front();
b.front().thisIsOnlyInB(); // Oh dear - item is really an A, so this explodes.
return 0;
这种问题在设计 C# 和 Java 的时候就已经被很好地理解了,语言设计者决定通过发出编译器错误来解决它。
C++ 一直有“信任程序员”的理念,所以它允许你进行强制转换——程序员要确保它不被滥用。 (如果 C++ 的发明晚于它的发明时间,它在该领域的设计是否会有所不同尚有争议。一些设计决策可能是当时编译器技术状态的结果。)
【讨论】:
好吧,我想说的是,任何带有重新解释的东西都应该被仔细审查,因为使用它通常是错误的(在这种情况下很明显) 哦,毫无疑问,即使在今天,C++ 也会将“信任程序员”作为原则。这是一种可以用来编写操作系统的语言,这是有意的。有危险的构造。另一方面,与 C 不同,这些结构很突出。危险的强制转换不只是一个类型名称和一个括号对(就像 C 那样),而是一个长关键字。向读者发出明确警告,表明正在发生一些不寻常的事情。【参考方案2】:因为泛型/模板化List
类型本质上是不变,而您的意图是将其用作协变 类型。
你可以使用任何类型 B 的对象,因为类型 B 是 A 的子类型,所以你可以使用任何类型 B 的对象。由于这种关系,你(错误地)期望任何泛型类型也保留这种关系。因此,在List<T>
示例中,您希望可以使用任何List<B>
,其中List<A>
是预期的,但这是不可能的,因为您不能保证程序的正确性,因为突然@987654325 @ 可以用A
更改。
C++、Java 和 C# 具有协变、逆变(“预期”的倒数)或不变性的概念。
如果您从 List 接口中删除 Add 方法,那么您可能能够构建一个协变类型,因为现在该类型没有可能破坏 List 类型正确性的方法,因此在 B 对象列表中不情愿地出现一个 C 类型的 cousin
对象。但是没有 Add 的 List 没有任何意义。
【讨论】:
【参考方案3】:区别基本上在于模板/泛型的作用。
尽管您的 C# 示例似乎没有进行强制转换,而是创建了一个新列表。 Java 我不确定,但它有一些运行时检查,由于性能开销,c++ 不会生成这些检查。
您还需要考虑的是,c++ 模板实际上会生成代码。因此,当您实例化 std::list<typeA>
时,将生成一个列表类,它与另一个类作为模板参数(即使是基类)的列表完全不同且不相关,因此它们不能相互转换。
【讨论】:
以上是关于请解释为啥在 Cpp、Java 和 C# 中允许或不允许 List<A> 强制转换为 List<A.super> [关闭]的主要内容,如果未能解决你的问题,请参考以下文章