C++是什么?相当于 java.lang.Object x = new Foo()?

发布于 2024-10-03 16:40:41 字数 64 浏览 1 评论 0原文

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

What is the C++ equivalent of java.lang.Object x = new Foo()?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

丶视觉 2024-10-10 16:40:41

C++ 中没有类似的东西,尝试用 C++ 编写 Java 程序是没有意义的。话虽这么说,我将从尝试尽可能多地模仿作业特征和声明精神的角度来处理这个问题。我建议的每种方法都有缺点和局限性。前两个并不是真正惯用的 C++,但了解它们以了解后两个解决了哪些问题很重要。

1. C 风格的 void 指针。

让我从最基本且最无用的 void 指针开始:

void* foo = new Foo();

任何东西都可以从 new 运算符分配给 void 指针,因为 new、placement new 等总是返回一个 void 指针。缺点应该是显而易见的:丢失有关所指向对象的类型信息。其一,C++ 缺乏反射或任何询问对象的方法。您必须将类型信息牢记在心,并通过来回转换才能实际使用它。由于没有类型安全的方法可以从 void 指针进行转换,因此可能会引发欢闹。

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

void* foo = some_function( _arg0 );

任何使用您的代码的作者都需要弄清楚应该发生什么。不幸的是,很多时候他们应该发生的事情和你,作者,认为应该从函数返回的东西是非常不同的。

2. C 风格联合

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

为了澄清上面的实际含义:

union myType{
    int a;
    char b[4];
};

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

3. Boost::Any

现在,如果你真的想要“我可以容纳任何东西”,那么使用 Boost::Any。这可以保存任何对象,而不会破坏大量非常有用的类型信息。 Boost 文档比我更清楚地阐述了它们的目的。摘自 Any 的介绍部分:

有时需要泛型(一般意义上的,而不是基于模板的编程)类型:真正可变的变量,容纳许多其他更具体类型的值,而不是 C++ 正常的严格和静态类型。

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

4. Boost::Variant

Boost::Variant 在不丢失对象信息的情况下解决与联合相同类型的问题。此外,它可以与非 POD 类型的对象一起使用。正如文档所述:

典型的解决方案具有对象的动态分配功能,这些对象随后通过公共基类型(通常是虚拟基类)进行操作Hen01 或者,更危险的是,一个 void*)。然后可以通过多态向下转换构造(例如,dynamic_cast、boost::any_cast 等)来检索具体类型的对象。

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

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

编辑:

重新组织以显示我回答OP时的想法和原因。我还在下面发表了评论。

There is no equivalent of this in C++ and it would be pointless to attempt to program Java in C++. That being said, I will approach this from a perspective of attempting to mimic as much of the assignment characteristics and spirit of the statement as possible. Each way I will suggest has downsides and limitations. The first two are not truly idiomatic C++ but it's important to know about them to see what problems the last two solved.

1. C-style void pointers.

Let me start with the most basic and least useful, a void pointer:

void* foo = new Foo();

Anything can be assigned to a void pointer from the new operator as new, placement new and the like always return a void pointer. The downsides should be obvious: loss of type information about the object pointed at. For one, C++ lacks reflection or any means of interrogating the object. You'd have to keep the type information in your head and use casting back and forth to actually use it. Since there's no type-safe way to cast from a void pointer, hilarity could ensue.

If this were a return type from a function:

void* foo = some_function( _arg0 );

any author using your code would need to figure out what should happen. Unfortunately, often times what they thing should happen and what you, the author, think should be returned from a function are very different.

2. C-style Unions

If you want to restrict yourself to N types which are supported instead of infinite types that java.lang.Object can handle then there are unions. These can hold a set of pre-defined value types on the same memory space as long as they are POD datatypes. Unions lack two very important things: the ability to know which value was assigned and the ability to hold non-POD types. This completely rules them out for use with any object with any sort of functionality such as std::string.

To clarify what the above actually means:

union myType{
    int a;
    char b[4];
};

If I set the first char within the "b" portion of an instance of "myType" then I also am setting the first byte of the int to that same value. In C++ these are really only useful for memory hacks and extremely low level programming (think embedded and the like.) They are not idiomatic C++.

3. Boost::Any

Now, if you truly want a "I can hold anything" then use a Boost::Any. This can hold any object without destroying a lot of type information which is so useful. The Boost documents state better than I in their purpose. Taken from the introduction section of Any:

There are times when a generic (in the sense of general as opposed to template-based programming) type is needed: variables that are truly variable, accommodating values of many other more specific types rather than C++'s normal strict and static types.

Think of Any solving many of the problems associated with a void pointer, such as loss of information about the contained object and the ability to safely cast to proper types.

4. Boost::Variant

Boost::Variant solves the same type of problem of that of a union without losing object information. Moreover it can be used with non-POD type objects. As the documentation states it best:

Typical solutions feature the dynamic-allocation of objects, which are subsequently manipulated through a common base type (often a virtual base class Hen01 or, more dangerously, a void*). Objects of concrete type may be then retrieved by way of a polymorphic downcast construct (e.g., dynamic_cast, boost::any_cast, etc.).

However, solutions of this sort are highly error-prone, due to the following:

  1. Downcast errors cannot be detected at compile-time. Thus, incorrect usage of downcast constructs will lead to bugs detectable only at run-time.
  2. Addition of new concrete types may be ignored. If a new concrete type is added to the hierarchy, existing downcast code will continue to work as-is, wholly ignoring the new type. Consequently, the programmer must manually locate and modify code at numerous locations, which often results in run-time errors that are difficult to find.

Edit:

Reorganized to show the what and the why of my thoughts when I answered the OP. I've also addressed comments below.

一场春暖 2024-10-10 16:40:41

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

C++ 中与 java.lang.Object x = new Foo() 最接近的等效项是使用 抽象基类 (ABC)。 ABC 是一个被设计为其他类的基类的类。您可以通过为班级提供至少一个 纯虚拟成员来创建 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

您还可以获得对 FooObject 引用,而不必担心 切片

int main()
{
  Foo my_foo;
  Object& obj_ref = my_foo; // OK
}

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

   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 的实际经验中,我经常发现我真正想要成为纯虚拟的唯一成员函数是析构函数。我设计的 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) 关于虚拟析构函数的规则也有例外。超出了本文的范围,但更好的经验法则是“基类析构函数应该是公共和虚拟,或受保护和非虚拟

There is no direct equivalent to java.lang.Object x = new Foo() because in C++, not everything is an object. But depending on how you want to use these Objects, you can accomplish the same goal.

The closest equivalent to java.lang.Object x = new Foo() in C++ is the use of Abstract Base Classes (ABC). An ABC is a class that is designed to be a base class to other classes. You create an ABC by giving your class at least one pure virtual member function, and you specify that by using this syntax:

class Object
{
public:
  virtual int my_func() = 0; // The "= 0" means "pure virtual"
};

A Pure Virtual member function typically has no implementation in the base class (See footnote *1). It is not possible to create an instance of an ABC:

int main()
{
  Object obj; // not possible because Object is an ABC
}

In order to use an ABC, you must create a subclass of it and implement each and every pure virtual member function in the derived class:

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

Now you can create an instance of Foo, while getting a pointer to the base class:

int main()
{
  Object* my_obj = new Foo;
}

The usual disclaimers apply in the above code about using smart pointers etc. I omitted this for clarity, but from now on I'll use shared_ptr.

You can also get an Object reference to Foo without having to fear slicing

int main()
{
  Foo my_foo;
  Object& obj_ref = my_foo; // OK
}

An important note about destructors and ABCs. When you implement an ABC, you often need to have a virtual destructor in the base class (Footnote *2). If you don't implement a virtual destructor in the base class, then when you try to delete an object through the base class pointer, you'll evoke undefined behavior, and this is bad.

   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
    }

In fact, in my real-world experience in implementing ABCs I often find that the only member function that I really want to be pure virtual is the destructor. ABCs I design often have many virtual methods that are not pure and then one virtual destructor. IMO (debatable), this is a good starting point when designing an ABC: Make the dtor pure, and keep a minimal number of non-pure virtual member functions in the base class, and provide an implementation for the pure virtual dtor in the base class. As you design this way you'll find things you can't do in your actual code, and that's when you deviate from this design.


Footnotes:


*1 ) Base classes can provide a definition for a pure virtual member function in the base class. But this is not the norm, and the reasons you might do this are somewhat beyond the scope of this post. Note that when you do this there is a special rule in the Standard that says you may not provide the definition along with the declaration; they must be seperate. Like this:

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) There are exceptions to the rule about having virtual destructors. Beyond the scope of this article, but a better rule of thumb is "A base class destructor should be either public and virtual, or protected and nonvirtual"

没有等效的,因为 Java 从托管堆中分配对象,而 C++ 在非托管内存中分配它们。 Java 中的对象由 JVM 使用标记和清除来跟踪自动垃圾回收,而 C++ 则需要显式释放所有内存。

运行时环境根本不同,由于相似的语法而进行类比是一个陷阱。

There is no equivalent because Java allocates objects from a managed heap, C++ allocates them in unmanaged memory. Objects in Java are tracked by the JVM for automatic garbage collection using mark-and-sweep, whereas C++ requires explicit release of all memory.

The runtime environments are fundamentally different, drawing analogies due to similar looking syntax is a trap.

娇女薄笑 2024-10-10 16:40:41
// 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;
*/
// 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;
*/
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文