【问题标题】:Differences between the C++ and the Java object model [closed]C++ 和 Java 对象模型之间的差异 [关闭]
【发布时间】:2011-05-23 05:58:05
【问题描述】:
  1. 在 Java 中,序列化对象非常容易。在 C++ 中,只有 memcpy 对象是安全的(?),只要它们像 C 结构(没有多态性)。在 C++ 中,如果编译器能够生成默认(平凡的)复制构造函数,那么为什么它不能生成用于自动序列化的代码?

  2. 在 Java 中,只能从 ctor 访问静态函数和数据成员。在 C++ 中,我可以愉快地使用 ctor 中的非静态成员和函数。

  3. 在 Java 中,我可以在类中内联初始化数据成员。在 C++ 中,这是一个编译错误。

  4. 在 Java 中,我可以初始化 ctor 中的 final 成员。在 C++ 中,我必须对初始化列表中的 const 成员进行初始化。 在 C++ 中,当控制到达 ctor 的主体时,ctor 的所有成员都已运行,对吧?

  5. 在 Java 中,一个 ctor 可以调用另一个 ctor。在 C++ 中我们不能这样做。

  6. 在 Java 中,this 直到 ctor 返回后才有效(this 引用的转义,多线程中的一个错误)。 this 在 C++ 中何时有效? this 可以在 C++ 和 Java 中轻松转义:在 ctor 中向 Listeners 注册尚未构造的对象(观察者模式)。

  7. 在 Java 中,我无法在派生类中将基类的公共函数设为私有。我很震惊地看到在 C++ 中它还可以,甚至有用。

谁能简要解释这些差异?

更新。试图收集到目前为止得到的答案。

  1. Boost 有一些类似序列化的支持。 (托尼)

  2. 尽管我搞砸了这一点,但 Alf P. Steinbach 还是举了一个有趣的例子。

  3. C++0x 将支持比 C++98 更实用的初始化。 (Alf P. Steinbach) #3 在 C++0x 中将是合法的 (Ken Bloom)

  4. 保证在构造函数自己的类中声明的数据成员在构造函数的 {body} 开始执行时已经完全构造。 (c++-faq-lite)

  5. C++0x 将允许构造函数调用其他对等构造函数(维基百科,C++0x)

  6. C++03 认为对象在其构造函数完成执行时被构造(维基百科)。

  7. 诸如访问控制之类的东西与对象模型几乎没有关系:这是访问控制系统的一个特性,它是一个编译时特性。 (Yttrill)

【问题讨论】:

  • 不,那时memcpy 他们甚至都不安全。 (另外,一个 ctor 可以在 C++0x 中调用另一个 ctor)。
  • 哦,Java 没有ctors——它们有构造函数。在 Java 中,他们更喜欢将所有内容都拼出来,而不是使用晦涩难懂的缩写——这是另一个哲学差异。
  • @Bill K:C++ 也喜欢把所有的东西都拼出来。 (即std::set_symmetric_differencestd::lexicographical_compare)。在 C 中看到缩短名称更为常见,因为 C 指定链接器名称只需要由前 8 个字符来区分。
  • #2 for Java 是错误的,我可以愉快地从其构造函数调用对象上的方法。 #6 对于 Java 也是错误的,允许 this 引用转义到构造函数中的另一个线程是完全合法的。当然,允许this 引用转义是相当不可取的。
  • #2 和#4 相互矛盾(提示:#2 错误)

标签: java c++ constructor


【解决方案1】:

在 Java 中,序列化对象非常容易。在 C++ 中,只要对象像 C 结构(没有多态性),memcpy 对象才是安全的(?)。

Java 是一种解释性语言(或者最近,如 Billy cmets,JIT 编译的),因此它别无选择,只能在运行时携带程序中每种数据类型的元数据包袱。在解释器、VM、可选编译器和元数据开销之间,Java 程序需要大量内存。 C++ 是一种编译语言,Java 做出的许多决定都是在编译时做出的,并且元数据不用于解释以指导运行时的序列化。通常,即使在编译时元数据也不会公开,这可能是因为不同的编译器供应商对程序的建模完全不同,而且他们没有集体协商合适的表示。这也是一项相当大的工作。 Bjarne Stroustrup 有一些关于如何公开这些信息的论文,但它甚至没有计划用于 C++0x。同时,通过少量手动标记,C++ 程序可以序列化对象 - 请参阅 boost 以获得良好的实现。

在 Java 中,我可以在类中内联初始化数据成员。在 C++ 中,这是一个编译错误。

每个 C++ 构造函数都提供了一个完整的、独立的对象将如何初始化的视图。如果值得的话,可以将常见的构造步骤考虑到支持例程中,但对它的调用在构造函数中仍然可见。必须检查分散在课堂上的各种作业会使其离域,尽管这肯定很方便。这里有很多东西,我会冒险。

在 Java 中,我可以初始化 ctor 中的最终成员。在 C++ 中,我必须对初始化列表中的 const 成员进行初始化。

这反映了 const 成员是用它们唯一的值创建的,不会从某个不确定或空状态转换到初始化状态。

在 Java 中,一个 ctor 可以调用另一个 ctor。在 C++ 中我们不能这样做。

是的,有时在 C++ 中有点烦人 - 特别是对于需要在初始化列表中的引用和常量,但您可以将其他常见的构造步骤纳入支持函数。我认为 C++ 的位置反映了构造函数在构造基、成员、指向虚拟调度表的指针等方面的工作——在机器代码级别上从另一个构造函数调用一个构造函数并执行正确的步骤并不一定是可能的。编译器可能需要生成第二个 callable-from-another-constructor 版本,或者内联被调用构造函数的正确部分,但这是一种隐藏的膨胀。

在 Java 中,我不能在派生类中将基类的公共函数设为私有。我很震惊地看到在 C++ 中它是可以的,甚至是有用的。

鉴于您承认它很有用,也许 Java 应该添加它。

谁能简要解释这些差异?

嗯,我试过了。

【讨论】:

  • "如果你承认它很有用,也许 Java 应该添加它。"
  • Java 字节码 may 需要 VM 才能运行,但 Java 仍处于编译状态。正如您的回答所暗示的那样,没有指定 C++ 编译成什么,而 Java 编译成什么,这就是 Java 中提供对象元数据的原因。
  • 我应该说通常编译。总是有 BeanShell。
  • Java 字节码通常是 JIT 编译的,而不是解释的。 Java 被解释的日子已经一去不复返了。
【解决方案2】:

其中许多都包含在设计理念中。

在很多情况下,Java 不允许危险或大多数时间没有意义的做法。例如,您不能从构造函数调用方法,因为在构造函数退出之前无法保证成员已被初始化。在构造函数完成之前,Java 尽量不让对对象的任何引用逃逸。

在 C++ 中,他们通常假设程序员知道他的行为的所有潜在后果,并让他们为所欲为。

Java 也抛弃了 C 的向后兼容性,您不能将 C 文件交给 Java 编译器。

其余的差异可能只是 C++ 是在 Java 之前设计的,而 Java 从人们使用 C++ 的问题中学到了一些东西。就像 C# 在某些情况下比 Java 具有更简洁的语法或更多功能一样,因为它们是从 Java 中学习的。

【讨论】:

  • 我一直认为它是“C/C++ 会接受任何字符串,并努力寻找某种方式将其解释为有效的表达式。毕竟,如果是程序员写的,它一定是对的……”。另一方面,我的朋友喜欢提醒我“C/C++ 是为真正的男人准备的——Java 让你变得虚弱......”
  • Java 1.0 于 1995 年左右发布。 C++ 于 1998 年底标准化。我认为您不能仅仅得出“Java 从 C++ 学习”的结论。它是双向的。在设计 Java 时,他们显然着眼于早期的准标准 C++,同样,在 C++ 标准化之前,他们同样明显地看到了 Java 的许多错误并从中吸取了教训。 (有趣的是,在标准化之前的过去几年中发生的很多事情都减少对 OOP 的重视。也许 Java 最终成为了一个警告?;))
  • Java 能够重写 C++ 由于兼容性而无法实现的许多东西——换句话说,在许多情况下,C++ 被阻止学习。例如——不管你同意与否——你怎么能消除指针并仍然称它为C++?在开发 Java 时,C++ 的使用相当广泛,因此 Java 确实从 C++ 中吸取了很多教训。
  • @Bert F 和汇编程序员会告诉你 C 让你变弱,C 程序员会告诉你 C++ 让你变弱。是的,它们都针对不同的空间,并且都对它们的空间有效。我在 Java 中所做的许多事情在 C++ 中将是一场噩梦,而且许多解决方案在 C 中的优化比在 Java 中要好得多(尤其是在内存方面)。
  • Err..“消除指针”意味着学习?如果有的话,C++ 强调使用指针(或类似对象的指针......即 STL)。无论如何,比较 C++ 和 Java 有点像比较苹果和橘子。两者都是通用编程语言,但 C++ 更倾向于系统编程,这是你永远不想用 10 英尺长的杆子接触 Java 的东西。
【解决方案3】:

它们是不同的语言。

【讨论】:

    【解决方案4】:

    OP 的标题有点混乱。访问控制之类的东西与对象模型几乎没有关系:这是访问控制系统的一个特性,它是一个编译时特性。对象构造的顺序对象模型的一部分。 C++ 和 Java 之间的主要区别实际上是 Java 是垃圾收集器,而 C++ 不是。

    另一个核心区别是 Java 使用古老的 crud 来实现多态性,而 C++ 使用了一个损坏的模板概念:在 C++ 中,模板是编译时间并且不影响对象模型,在 Java 中,多态性是围绕 C++ 中所谓的动态演员表。

    另一个主要的核心区别是 Java 支持线程,而在 C++ 中您必须使用某种库。

    还有较低级别的类型系统差异:Java 具有最少的静态类型,但基本上是一种动态语言。另一方面,C++ 几乎完全是一种静态类型语言,除了例外..:)

    【讨论】:

      【解决方案5】:

      一点Java:

      class Base
      {
          public static void say( String s )
          {
              System.out.println( s );
          }
      
          public void sayHello()
          {
              say( "Base.sayHello says..." );
              say( "Base hello" );
          }
      
          public Base()
          {
              sayHello();
          }
      }
      
      class Derived
          extends Base
      {
          private String[] myWords;   // = new String[]{ "Derived", "hello" };
      
          public void sayHello()
          {
              say( "Derived.sayHello says..." );
              say( myWords[0] + " " + myWords[1] );
          }
      
          Derived()
          {
              myWords = new String[]{ "Derived", "hello" };
          }
      }
      
      class ConsCallDemo
      {
          public static void main( String[] args )
          {
              Derived o = new Derived();
          }
      }
      

      输出:

      Derived.sayHello 说…… 线程“主”java.lang.NullPointerException 中的异常 在 Derived.sayHello(conscall.java:28) 在基地。(conscall.java:16) 在派生。(conscall.java:32) 在 ConsCallDemo.main(conscall.java:41)

      问题在于,在 Java 中,即使在初始化该实例之前,调用也会转到实例化对象的类。

      C++ 通过在构造和销毁期间动态调整对象的类型来防范这个问题。在 C++ 中,当 Base 构造函数执行时,对象的类型为 Base。因此,来自Base 构造函数的调用行为是对象是从类Base 实例化的,而不管实际实例化类是什么。

      例子,只是将上面的Java代码翻译成C++:

      #include <iostream>
      #include <string>
      #include <vector>
      using namespace std;
      
      void say( string const& s )
      {
          cout << s << endl;
      }
      
      class Base
      {
      public:
          virtual void sayHello() const
          {
              say( "Base::sayHello says..." );
              say( "Base hello" );
          }
      
          Base()
          {
              sayHello();
          }
      };
      
      class Derived
          : Base
      {
      private:
          vector< string >    words_;
      
      public:
          void sayHello() const
          {
              say( "Derived::sayHello says..." );
              say( words_[0] + " " + words_[1] );
          }
      
          Derived()
          {
              words_.push_back( "Derived" );
              words_.push_back( "hello" );
          }
      };
      
      int main()
      {
          Derived o;
      }
      

      输出:

      Base::sayHello 说…… 基地你好

      一种在 C++ 中获得可能预期效果的方法,这也建议了如何在 Java 中(正确地)做到这一点:

      #include <iostream>
      #include <string>
      #include <vector>
      #include <boost/shared_ptr.hpp>
      using namespace std;
      
      void say( string const& s )
      {
          cout << s << endl;
      }
      
      class Base
      {
      protected:
          struct Greeter
          {
              typedef boost::shared_ptr< Greeter >    Ptr;
      
              virtual void sayHello() const
              {
                  say( "Base::sayHello says..." );
                  say( "Base hello" );
              }
          };
      
      private:
          Greeter::Ptr    greeter_;
      
      public:
          void sayHello() const
          {
              greeter_->sayHello();
          }
      
          Base( Greeter::Ptr greeter = Greeter::Ptr( new Greeter ) )
              : greeter_( greeter )
          {
              sayHello();
          }
      };
      
      class Derived
          : Base
      {
      protected:
          struct Greeter
              : Base::Greeter
          {
              vector< string >    words_;
      
              virtual void sayHello() const
              {
                  say( "Derived::sayHello says..." );
                  say( words_[0] + " " + words_[1] );
              }
      
              Greeter()
              {
                  words_.push_back( "Derived" );
                  words_.push_back( "hello" );
              }
          };
      
      public:
          Derived()
              : Base( Greeter::Ptr( new Greeter ) )
          {}
      };
      
      int main()
      {
          Derived o;
      }
      

      输出:

      派生::sayHello 说…… 衍生你好

      关于你的其他观点,在这里讨论太多了。请注意,C++0x 将支持比 C++98 更实用的初始化。而且你的第 7 点的正式方面有点争议,有一个关于它的缺陷报告(如注释参考手册所述,它在标准前 C++ 中是不允许的,但显然是为了在标准 C98 中允许,但是,措辞有点自相矛盾)。

      干杯,

      【讨论】:

        【解决方案6】:

        大约 7 点:

        在 Java 中,我不能在派生类中将基类的公共函数设为私有。我很震惊地看到在 C++ 中它是可以的,甚至是有用的。

        如果您能够隐藏一个超类方法,则不能保证您的方法不能被用户调用。总是可以转换为超类并调用该方法,因为 Java 中的所有非静态方法都是 C++ 虚拟方法,您的子类方法实际上会被调用。

        它适用于静态方法,但我认为覆盖静态方法不是一个好习惯。

        需要在子类中隐藏方法通常意味着糟糕的设计,但如果你真的需要,你应该抛出异常。不利的一面是它是运行时的,但是当调用发生时它会崩溃。 而在 C++ 中,如果您隐藏一个方法但强制转换为父类,如果它的虚拟方法将实际被调用,并且如果它是静态链接的父类方法将被调用,那么两者可能都不是您想要的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多