【问题标题】:Why C++ don't have Java style type erased generics? [closed]为什么 C++ 没有 Java 风格的类型擦除泛型? [关闭]
【发布时间】:2021-02-10 14:33:32
【问题描述】:

Java 泛型和 C++ 模板都有其优点和缺点。 Java 泛型生成的代码更少,有助于最小化可执行文件的大小,而 C++ 为模板的每个不同实例重写整个模板代码。另一方面,C++ 模板支持基本数据类型甚至常量类型作为参数实参,而 Java 只支持类作为泛型的参数。

为什么 C++ 不能两者兼得?当用户想要在函数中使用类型时,他可以使用 C++ 样式模板,否则他可以在声明参数类/函数时使用 Java 样式泛型。什么技术性阻碍了这个想法的实施?

编辑:例如考虑一下:

struct A {
    virtual void do_something() {}
};

struct B: public A {
    virtual void do_something() {}
};

template <typename T>
void call_do_something(T arg) {
    arg.do_something();
}

int main() {
    A a;
    call_do_something(a);
    B b;
    call_do_something(b);
}
class A {
    public void do_something() {}
}

class B extends A {
    public void do_something() {}
}

class Z {
    public static void <T extends A> call_do_something(T arg) {
        arg.do_something();
    }
}

public class Main {
    public static void main(String[] args) {
        Z.call_do_something(new A());
        Z.call_do_something(new B());
    }
}

据我所知,在编译时,上面的 C++ 代码会生成两个版本的call_do_something,一个用于 A,一个用于 B。但下面的 java 中的等效代码只会生成一个 call_do_something。这将减少生成的可执行文件大小。

我并不是说 Java 泛型优于 C++ 模板。我只是想知道为什么这不是在 C++ 中实现的,是否有技术原因,是否在过去提出过等等。抱歉造成混淆。


编辑:当我不深入了解这些概念时,有人问了这个问题。现在我认为我对 C++ 中的模板和 Java 中的泛型有了更好的理解,我正在尝试自己回答这个问题。

Java 泛型可以通过使用类型擦除而受益,因为它具有单根类层次结构,并且对象总是通过引用传递(请注意,通过值传递的类型,如 intfloat 不能在泛型中使用) . Java 编译器会擦除类型参数的实际泛型类型,并将其替换为参数的上限。 Java 的泛型与 C++ 中的运行时多态性更相关。

另一方面,C++ 模板比 Java 泛型可以做的更多。它可以按值调用,可以用两个不相关的类实例化模板等。但它不能实现类型擦除,因为对象可以按值传递,完成后,它们的大小和子结构会发生变化,单个机器代码无法处理它们全部。

但在特定情况下,如果您有一个公共基类,并且您通过引用传递对象,那么您不应该使用 C++ 中的模板。您应该改用虚函数的运行时多态性。

以下Java中的泛型代码实际上等价于:

class AList<E extends A> {
    private List<E> list = new ArrayList<E>();

    public E add(E ele) {
        list.add(ele);
        return ele;
    }
}

public class Main {
    public static void main(String[] args) {
        AList<B> l = new AList();
        B b = l.add(new B());
    }
}

Java 中的这个:

class AList {
    private List<A> list = new ArrayList<A>();

    public void add(A ele) {
        list.add(ele);
    }
}

public class Main {
    public static void main(String[] args) {
        AList l = new AList();
        B b = (B)l.add(new B());
    }
}

这在 C++ 中:

class AList {
private:
    std::vector<A&> vec = {};

public:
    A& add(A& ele) {
        vec.push_back(ele);
        return ele;
    }
}

int main() {
    AList l;
    B btemp;
    B& b = static_cast<B&>(l.add(&btemp));
}

TL;DR 类型擦除在 Java 泛型中是有效的,因为它具有单根类层次结构并且对象总是通过引用传递。由于这些在 C++ 中不是这样,类型擦除不适合 C++ 模板。

我想把这个放在答案中。但是由于问题已关闭,并且我尝试重新打开失败,因此我将其添加到此处。

【问题讨论】:

  • C++ 有类型擦除。
  • Java's type erasure has clear advantages [需要引用]。
  • @MikeVine Java 的类型擦除具有明显的优势 - eerorika on stackoverflow。平心而论,它也有明显的缺点。
  • Mainly Reduced Generated Code。这有一些警告。如果模板的 2 个实例化的代码会生成完全相同的字节码,那么编译器可能会共享它们(参见 opt:ICF)。所以 C++ 和 Java 一样好。如果实例化的代码不同,那么根据定义,如果您共享实例化,您将为其中之一生成效率较低的代码。所以在这种情况下,C++ 比 Java 更好(更快)。所以 C++ >= Java(稍微开玩笑)
  • 函数应该写成void call_do_something(A&amp; arg)。不需要模板。需要一个更好的例子。

标签: c++ templates generics type-erasure


【解决方案1】:

你认为的优势,其他人可能会认为是劣势。例如,类型擦除访问意味着您为访问付出了性能损失,并且无法在编译时进行静态类型检查。

C++ 中有类型擦除的函数对象 - std::function,因此您可以在代码中使用它们,但请记住,这样做有缺点。

【讨论】:

  • 除了我在问题中提到的限制之外,还有什么缺点?擦除的类型总是绑定到一个基类,因此静态检查可以在编译时在该范围内完成......我在这里解释可能不清楚,但我正在与 java 泛型进行比较。
  • @SouravKannanthaB 既然你问的很笼统,我就笼统地回答。如果您想要更多定制的答案,您可以在 Java 中提供一个您认为生成更好代码的 sn-p,并询问如何在 C++ 中实现相同的效率。
  • 据我所知,创建一个模板函数,并用两个不同的类调用它,会产生两组生成的代码,每组代码用于每种情况。但是如果这两个类有一个共同的祖先,并且函数只依赖于在那里声明的成员,那么使用类型擦除将减少生成的代码大小。
  • @SouravKannanthaB 在您的示例中,只需将其设为 void call_do_something(A&amp; arg)。不确定“更多编译时类型安全”是什么意思。顺便说一句,很抱歉给这个答案发垃圾邮件;)
  • @SouravKannanthaB 有时需要它,它可以在 C++ 中完成。 “究竟是什么阻止了这一切?” - 什么都没有。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
相关资源
最近更新 更多