请解释为什么列表<A>投射到 List<A.super>在 Cpp、Java 和 C# 中允许或不允许
Please explain why List<A> cast to List<A.super> allowed or not allowed in Cpp, Java and C#
我发现: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;
}
java 2.
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));
}
c# 3. 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]);
}
你能给我解释一下吗?谢谢!
这里发生的事情是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++被发明得晚一些,它在这方面的设计是否会有所不同,这是一个值得讨论的问题。一些设计决策可能是当时编译器技术状态的结果。
因为泛型/模板化的List
类型本质上是不变的,而您的意图是将其用作协变类型。
你可以使用类型B的任何对象,因为类型B是A的子类型,因为这种关系,你(错误地)期望任何泛型类型也保持这种关系。因此,在List<T>
的例子中,您期望可以在List<A>
需要的地方使用任何List<B>
,但这是不可能的,因为您无法保证程序的正确性,因为突然List<B>
可以被A
更改。
如果您从List接口中删除Add方法,那么您可能能够构建协变类型,因为现在该类型没有可能破坏List类型正确性的方法,因此在B对象列表中不情愿地出现c类型的cousin
对象,但是没有Add的List是没有意义的。
区别基本上在于模板/泛型的作用。
虽然你的例子从c#似乎不做强制转换,但创建一个新的列表。Java我不确定,但它有一些运行时检查,c++不生成由于性能开销。
您还需要考虑的是c++模板实际上是生成代码的。因此,当您实例化std::list<typeA>
时,将生成一个列表类,这是一个完全不同且不相关的类,与另一个类作为模板参数的列表(即使是基类),因此它们不能相互转换。
- 请解释这句话(cout<<1+int((a<b)^((b-a)&1) )<<endl
- 呼叫运营商<<临时
- 如何防止clang格式在流运算符调用之间添加换行符<<
- <<操作员在下面的行中工作
- EASTL矢量<向量<int>>连续的
- C - 创建矢量&lt; vector&lt; double&gt;&gt;矩阵具有分配而不是inizializ
- 为什么将此对向量&lt; map&lt; int,int&gt;&gt;中的地图进行更新.失败
- C :对矢量进行排序&lt; struct&gt;(结构有2个整数)基于结构的整数之一
- 明确的专业化“ CheckIntmap&lt;&gt;”实例化
- 什么是模板&lt;&gt;inline bla bla
- 编辑C Qlist&lt; object*&gt; gt;QML代码和一些QML警告中的模型
- eigen :: llt&lt;eigen :: matrixxd&gt;具有不完整的类型
- 错误,包括&lt; ctype&gt;在原子上使用C 11
- std::vector<;uint8_t>;当C++11/14启用时,手动复制而不是调用memcpy
- 如何加入向量&lt; int&gt;到C 中的单个INT
- 是std :: set&lt; std :: future&gt;不可能存在
- 是numeric_limits&lt; int&gt; :: is_modulo从逻辑上矛盾
- opencv 2.4.7在iOS错误背景_segm.hpp #include&lt; list&gt;未找到
- 在修改列表后,std :: list&lt; t&gt; :: end()的值是否会更改
- ///<评论></评论>在Visual Studio中