请解释为啥在 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&lt;String&gt; 转换为 List&lt;Object&gt;,因为您可以将 Object 添加到 List&lt;Object&gt;,但不能添加到 List&lt;String&gt; 因为 Java 和 C# 不允许您以这种方式射击自己的脚,但 C++ 允许您制作尽可能多的脚副本,并同时在所有脚上射击... 这是 3 个不同的问题。分开回答会更容易 @talex 我认为将它们标记为 3 个单独的重复项会更容易:) @neohope 因为List 是原始类型,编译器不再知道元素类型的限制;它可能转换为List&lt;Object&gt; 是安全的,因此它允许强制转换,只是带有警告。 【参考方案1】:

这里发生的情况是,C# 和 Java 可以防止你犯下严重的错误,但 C++ 允许你这样做(因为语言的设计方式)。

这里有一些示例 C++ 代码演示了可能出现的问题。它与您的示例代码非常相似,只是稍作调整:在将list&lt;B&gt; 转换为list&lt;A&gt; 之后,它会将A 类型的元素推到列表中。然后它会尝试通过现有的list&lt;B&gt; 访问该元素,并获得可预测的结果(即它会在你的脸上爆炸):

#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&lt;T&gt; 示例中,您希望可以使用任何List&lt;B&gt;,其中List&lt;A&gt; 是预期的,但这是不可能的,因为您不能保证程序的正确性,因为突然@987654325 @ 可以用A 更改。

C++、Java 和 C# 具有协变、逆变(“预期”的倒数)或不变性的概念。

如果您从 List 接口中删除 Add 方法,那么您可能能够构建一个协变类型,因为现在该类型没有可能破坏 List 类型正确性的方法,因此在 B 对象列表中不情愿地出现一个 C 类型的 cousin 对象。但是没有 Add 的 List 没有任何意义。

【讨论】:

【参考方案3】:

区别基本上在于模板/泛型的作用。

尽管您的 C# 示例似乎没有进行强制转换,而是创建了一个新列表。 Java 我不确定,但它有一些运行时检查,由于性能开销,c++ 不会生成这些检查。

您还需要考虑的是,c++ 模板实际上会生成代码。因此,当您实例化 std::list&lt;typeA&gt; 时,将生成一个列表类,它与另一个类作为模板参数(即使是基类)的列表完全不同且不相关,因此它们不能相互转换。

【讨论】:

以上是关于请解释为啥在 Cpp、Java 和 C# 中允许或不允许 List<A> 强制转换为 List<A.super> [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

为啥列表中允许使用尾随逗号?

为啥在 python 切片中允许非整数内置类型?

c_cpp c ++中允许字符串扩展的字符串类

主键中允许 NULL - 为啥以及在哪个 DBMS 中?

为啥在 JavaScript 的 IF 语句中允许重新声明变量

如何在 oauth2 身份验证之上实现用户权限