【发布时间】:2011-05-13 02:22:15
【问题描述】:
java.lang.Object x = new Foo() 的 C++ 等价物是什么?
【问题讨论】:
标签: java c++ object constructor
java.lang.Object x = new Foo() 的 C++ 等价物是什么?
【问题讨论】:
标签: java c++ object constructor
// Test this
namespace external
{
template <class T>
struct var
{
private:
T *value;
public:
var(T *value)
{
this->value = value;
};
T operator &() {
return this-> value;
};
};
}
#define var external::var<void *>
/**
Use:
var i = 0;
cout << &i; // Output 0;
*/
【讨论】:
没有等价物,因为 Java 从托管堆中分配对象,而 C++ 在非托管内存中分配它们。 Java 中的对象由 JVM 跟踪以使用标记和清除进行自动垃圾收集,而 C++ 需要显式释放所有内存。
运行时环境根本不同,由于看起来相似的语法而进行类比是一个陷阱。
【讨论】:
delete x; 明确地销毁了对象,并且作为其中的一部分隐式释放了内存。
new X; 创建的对象),它在第二句中对 C++ 行为的普遍性过于自信。
没有与java.lang.Object x = new Foo() 等效的直接,因为在C++ 中,并非所有事物都是对象。但是取决于您想如何使用这些Objects,您可以实现相同的目标。
在 C++ 中与 java.lang.Object x = new Foo() 最接近的是使用 Abstract Base Classes (ABC)。 ABC 是一个被设计为其他类的基类的类。您通过为您的班级提供至少一个pure virtual member function 来创建一个 ABC,并使用以下语法指定它:
class Object
{
public:
virtual int my_func() = 0; // The "= 0" means "pure virtual"
};
纯虚成员函数通常在基类中没有实现(见脚注 *1)。无法创建 ABC 的实例:
int main()
{
Object obj; // not possible because Object is an ABC
}
为了使用 ABC,您必须创建它的子类并在派生类中实现每个纯虚成员函数:
class Foo : public Object
{
public:
int my_func() { return 42; } // use of "virtual" is assumed here
};
现在您可以创建Foo 的实例,同时获取指向基类的指针:
int main()
{
Object* my_obj = new Foo;
}
通常的免责声明适用于上述代码中关于使用智能指针等的内容。为了清楚起见,我省略了这一点,但从现在开始我将使用shared_ptr。
您还可以获得Object 对Foo 的引用,而不必担心slicing
int main()
{
Foo my_foo;
Object& obj_ref = my_foo; // OK
}
关于析构函数和 ABC 的重要说明。当您实现 ABC 时,经常需要在基类中有一个虚拟析构函数(脚注 *2)。如果你没有在基类中实现虚析构函数,那么当你尝试通过基类指针delete一个对象时,你会evoke undefined behavior,这很糟糕。
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
}
事实上,在我实现 ABC 的实际经验中,我经常发现我真正想要成为纯虚拟的 only 成员函数是析构函数。我设计的 ABC 经常有很多不纯的虚方法,然后是一个虚析构函数。 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;
}
*2) 关于拥有虚拟析构函数的规则有一些例外。超出了本文的范围,但更好的经验法则是“A base class destructor should be either public and virtual, or protected and nonvirtual”
【讨论】:
C++ 中没有类似的东西,尝试用 C++ 编写 Java 是没有意义的。话虽如此,我将从尝试尽可能多地模仿语句的分配特征和精神的角度来处理这个问题。我建议的每种方式都有缺点和局限性。前两个不是真正地道的 C++,但了解它们以了解后两个解决了什么问题很重要。
1. C 风格的 void 指针。
让我从最基本和最没用的一个空指针开始:
void* foo = new Foo();
任何东西都可以从 new 运算符分配给一个 void 指针,因为 new、placement new 等总是返回一个 void 指针。缺点应该很明显:丢失有关所指向对象的类型信息。一方面,C++ 缺乏反射或任何询问对象的方法。您必须将类型信息保留在脑海中并使用来回转换来实际使用它。由于没有类型安全的方法可以从 void 指针进行转换,因此可能会引起欢闹。
如果这是一个函数的返回类型:
void* foo = some_function( _arg0 );
任何使用您的代码的作者都需要弄清楚应该发生什么。不幸的是,很多时候他们应该发生的事情和你,作者认为应该从函数返回的事情是非常不同的。
2。 C 风格联合
如果你想限制自己使用 N 种受支持的类型,而不是 java.lang.Object 可以处理的无限类型,那么有unions。它们可以在同一内存空间上保存一组预定义的值类型只要它们是 POD 数据类型。联合缺乏两个非常重要的东西:知道分配了哪个值的能力和保存非 POD 类型的能力。这完全排除了它们与任何具有任何功能的对象(例如std::string)一起使用的可能性。
为了澄清上面的实际含义:
union myType{
int a;
char b[4];
};
如果我在“myType”实例的“b”部分设置第一个字符,那么我也将 int 的第一个字节设置为相同的值。在 C++ 中,这些实际上只对内存黑客和极低级编程(想想嵌入式等)有用。它们不是惯用的 C++。
3. Boost::Any
现在,如果您真的想要“我可以拥有任何东西”,请使用Boost::Any。这可以保存任何对象,而不会破坏大量非常有用的类型信息。 Boost 文件的目的比我更好。摘自 Any 的介绍部分:
有时需要泛型(在一般意义上与基于模板的编程相反)类型:真正可变的变量,容纳许多其他更具体类型的值,而不是 C++ 的普通严格和静态类型。
想一想解决许多与 void 指针相关的问题,例如丢失有关所包含对象的信息以及安全地转换为正确类型的能力。
4. Boost::Variant
Boost::Variant 在不丢失对象信息的情况下解决了与联合问题相同类型的问题。此外,它可以与非 POD 类型的对象一起使用。正如文档所说的那样:
典型的解决方案以对象的动态分配为特色,这些对象随后通过一个通用的基类型(通常是虚拟基类Hen01,或者更危险的是 void*)进行操作。然后可以通过多态向下转换构造(例如,dynamic_cast、boost::any_cast 等)检索具体类型的对象。
但是,由于以下原因,此类解决方案极易出错:
- 在编译时无法检测到向下转换错误。因此,向下转换构造的错误使用将导致只能在运行时检测到的错误。
- 可以忽略添加新的具体类型。如果将新的具体类型添加到层次结构中,现有的向下转换代码将继续按原样工作,完全忽略新类型。因此,程序员必须手动定位和修改多个位置的代码,这通常会导致难以发现的运行时错误。
编辑:
重新组织以显示我在回答 OP 时的想法和原因。我还在下面提到了 cmets。
【讨论】:
Object 转换为其他内容,如果转换无效,则可以恢复。正如您所说,在 C++ 中,无法从 void* 动态转换。
your_pointer 的类型为void* 时,这样做是非法的。