什么是java.lang.Object的C++等价物x = new Foo()?

fre*_*low 6 c++ java constructor object

什么是C++等价物java.lang.Object x = new Foo()

whe*_*ies 19

在C++中没有相同的东西,尝试用C++编写Java是没有意义的.话虽如此,我将从试图模仿尽可能多的赋值特征和陈述精神的角度来处理这个问题.我建议的每种方式都有缺点和局限.前两个并不是真正惯用的C++,但了解它们以了解最后两个解决了哪些问题非常重要.

1. C风格的无效指针.

让我从最基本和最不实用的空指针开始:

void* foo = new Foo();
Run Code Online (Sandbox Code Playgroud)

可以将任何内容分配给来自new运算符的void指针,因为new,placement new等总是返回一个void指针.缺点应该是显而易见的:丢失有关指向的对象的类型信息.首先,C++缺乏反思或任何询问对象的方法.您必须将类型信息保留在脑中并使用来回进行实际使用.由于没有类型安全的方法来从空指针进行强制转换,因此可能会出现欢闹.

如果这是函数的返回类型:

void* foo = some_function( _arg0 );
Run Code Online (Sandbox Code Playgroud)

任何使用您的代码的作者都需要弄清楚应该发生什么.不幸的是,通常它们应该发生的事情以及作者认为应该从函数返回的内容是非常不同的.

2. C风格的工会

如果你想限制你自己支持的N种类型而不是java.lang.Object可以处理的无限类型,那么就有了联合.只要它们是POD数据类型,它们就可以在同一个内存空间中保存一组预定义的值类型.工会缺少两个非常重要的事情:能够知道分配了哪个值以及保持非POD类型的能力.这完全排除了它们与任何具有任何功能的对象一起使用,例如std::string.

澄清上述内容实际意味着什么:

union myType{
    int a;
    char b[4];
};
Run Code Online (Sandbox Code Playgroud)

如果我在"myType"实例的"b"部分中设置第一个char,那么我也将int的第一个字节设置为相同的值.在C++中,这些实际上只对内存破解和极低级别编程有用(想想嵌入式等).它们不是惯用的C++.

3.提升::任何

现在,如果你真的想要"我可以拥有任何东西",那么使用Boost :: Any.这可以容纳任何对象而不会破坏很多非常有用的类型信息.Boost文件在其目的上比我更好.取自Any的介绍部分:

有时需要通用(在一般意义上而不是基于模板的编程)类型:变量是真正可变的,适应许多其他更具体类型的值而不是C++的普通严格和静态类型.

想想任何解决与void指针相关的许多问题,例如丢失有关所包含对象的信息以及安全地转换为正确类型的能力.

4. Boost :: Variant

Boost :: Variant解决了与union相同类型的问题,而不会丢失对象信息.此外,它可以与非POD类型的对象一起使用.正如文档所述:

典型的解决方案以对象的动态分配为特征,随后通过公共基类型(通常是虚拟基类Hen01或更危险的,空白*)进行操作.然后可以通过多态下行构造(例如,dynamic_cast,boost :: any_cast等)来检索具体类型的对象.

但是,由于以下原因,此类解决方案极易出错:

  1. 在编译时无法检测到Downcast错误.因此,不正确使用向下转换结构将导致仅在运行时检测到错误.
  2. 可以忽略添加新的混凝土类型.如果将新的具体类型添加到层次结构中,则现有的向下转码将继续按原样工作,完全忽略新类型.因此,程序员必须在多个位置手动定位和修改代码,这通常会导致难以找到的运行时错误.

编辑:

重新组织以显示当我回答OP时我的想法是什么以及为什么.我也在下面发表评论.


Joh*_*ing 10

没有直接等价于java.lang.Object x = new Foo()因为在C++中,并非所有东西都是对象.但是,根据您希望如何使用这些Object,您可以实现相同的目标.

java.lang.Object x = new Foo()C++ 最接近的是使用Abstract Base Classes(ABC).ABC是一个被设计为其他类的基类的类.您通过为您的类提供至少一个纯虚拟成员函数来创建ABC ,并使用以下语法指定:

class Object
{
public:
  virtual int my_func() = 0; // The "= 0" means "pure virtual"
};
Run Code Online (Sandbox Code Playgroud)

Pure Virtual成员函数通常在基类中没有实现(参见脚注*1).无法创建ABC的实例:

int main()
{
  Object obj; // not possible because Object is an ABC
}
Run Code Online (Sandbox Code Playgroud)

要使用ABC,您必须创建它的子类并在派生类中实现每个纯虚拟成员函数:

class Foo : public Object
{
public: 
  int my_func() { return 42; } // use of "virtual" is assumed here
};
Run Code Online (Sandbox Code Playgroud)

现在,您可以创建一个实例Foo,同时获取指向基类的指针:

int main()
{
  Object* my_obj = new Foo;
}
Run Code Online (Sandbox Code Playgroud)

通常的免责声明在上面的代码中适用于使用智能指针等.为了清楚起见,我省略了这一点,但从现在开始我将使用shared_ptr.

您也可以Object参考,Foo而不必担心slicing

int main()
{
  Foo my_foo;
  Object& obj_ref = my_foo; // OK
}
Run Code Online (Sandbox Code Playgroud)

关于析构函数和ABCs的重要说明.实现ABC时,通常需要在基类中使用虚拟析构函数(脚注*2).如果您没有在基类中实现虚拟析构函数,那么当您delete通过基类指针尝试对象时,您将引发未定义的行为,这很糟糕.

   class Object
    {
    public:
      virtual int my_func() = 0;
    };
    class Foo : public Object
    {
    public: 
      int my_func() { return 42; } 
    };

    int main()
    {
      Object* obj = new Foo;
      delete obj;  // Undefined Behavior: Object has no virtual destructor
    }
Run Code Online (Sandbox Code Playgroud)

事实上,在我实现ABCs的实际经验中,我经常发现我真正想成为纯虚拟的唯一成员函数是析构函数.我设计的ABCs通常有许多不纯的虚拟方法,然后是一个虚拟析构函数.IMO(有争议),这是设计ABC时的一个很好的起点:使dtor保持纯净,并在基类中保留最少数量的非纯虚拟成员函数,并为基础中的纯虚拟dtor提供实现类.当你以这种方式设计时,你会发现在实际代码中你无法做到的事情,那就是你偏离这个设计的时候.


脚注:


*1)基类可以提供一种纯虚成员函数的定义中的基类.但这不是常态,你可能会这样做的原因有些超出了这篇文章的范围.请注意,当您执行此操作时,标准中有一条特殊规则,即您可能不会在声明中提供定义; 他们必须分开.像这样:

class Object
{
public:
  virtual int my_funky_method() = 0;
  virtual bool is_this_ok() = 0 { return false; } // ERROR: Defn not allowed here
};

int Object::my_funky_method()
{
  return 43;
}
Run Code Online (Sandbox Code Playgroud)

*2)有关虚拟析构函数的规则有例外.超出本文的范围,但更好的经验法则是" 基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的 "