【问题标题】:Does C++ require a destructor call for each placement new?C++ 是否需要为每个新位置调用析构函数?
【发布时间】:2011-01-03 16:49:18
【问题描述】:

我了解放置新调用通常与对析构函数的显式调用相匹配。我的问题是:如果我不需要析构函数(没有代码放在那里,也没有具有析构函数的成员变量)我可以安全地跳过显式析构函数调用吗?

这是我的用例:我想为 C API 编写 C++ 绑定。在 C API 中,许多对象只能通过指针访问。而不是创建一个包含单个指针的包装对象(这很浪费并且在语义上令人困惑)。我想 使用placement new 在C 对象的地址处构造一个对象。 C++ 对象在其构造函数或析构函数中什么都不做,它的方法除了委托给 C 方法之外什么也不做。 C++ 对象将不包含虚方法。

我对这个问题有两个部分。

  1. 这个想法在任何生产编译器的实践中都行不通吗?

  2. 这在技术上是否违反 C++ 语言规范?

【问题讨论】:

  • 新位置是您应该这样做的罕见时间之一。
  • @Josh:我不认为我理解这个问题。为什么不将 C 对象作为 C++ 对象的唯一数据成员?然后在您的成员函数中,将指向该成员的指针传递给您委托的 C API 函数。
  • @Josh: 或者,您可以使用shared_ptr(或者更确切地说,一个包装器)作为“句柄”,如果需要根据 C API 正确清理,可以使用自定义删除器.这在逻辑上是一个指针并且表现得像一个值,但具有熟悉的好处。但是,从您为那些共享所有权语义付费的意义上说,这是“浪费”的,但与世界上所有其他 shared_ptr 的使用一样......
  • @John Haberman:绝对不是。放置 new 将破坏您的旧对象。
  • @Charles:也许最好说“放置新的位置会抹掉你的旧对象”。当您说“销毁”时,它似乎暗示它将调用原始对象的析构函数,但它不会这样做。

标签: c++


【解决方案1】:

如果我正确理解了您的问题,您的内存中有一个 C 对象,并且您想用现有对象“顶部”的相同布局初始化一个 C++ 对象。

CppObject* cppobject = new (cobject) CppObject;

虽然不为旧对象调用析构函数没有问题 - 这是否会导致资源泄漏或其他问题完全取决于旧对象的类型,并且是用户代码问题,而不是语言一致性问题 -事实上,您将内存重用于新对象意味着不再可以访问旧对象。

尽管operator new 的放置形式必须只返回给定的地址,但在调用任何构造函数(如果有的话)之前,没有什么可以阻止新表达式本身为新对象擦除内存。未根据 C++ 语言规则初始化的新对象的成员具有未指定的内容,这绝对不意味着具有曾经存在于被重用内存中的任何旧对象的内容。

如果我对您的理解正确,则不能保证您尝试执行的操作。

【讨论】:

  • 标准停止放置 new 擦除 ctor 之前的内存:“故意不执行其他操作。” [§18.4.1.3p3, C++03] 除非我误解了。
  • @Fred Nurk:不,这是::operator new 的放置表单的要求,可以通过显式调用::operator new 来验证。一个新的表达式初始化一个新的对象;没有什么可以阻止编译器在调用适当的operator new 之后和初始化对象以重置内存之前插入魔法,或者当对象的某些部分“未初始化”时,用垃圾值填充这些部分。跨度>
【解决方案2】:

我认为答案是,如果你的类是 POD(如果它确实在 con/destructor 中什么都不做,没有虚拟成员函数,并且没有任何非静态数据成员)事情),那么你不需要调用构造函数析构函数,它的生命周期只是底层内存的生命周期。你可以像C中使用struct一样使用它,不管它是否被构造,你都可以调用它的成员函数。

【讨论】:

  • 事实上,对象是否是 POD 并不重要。任何对象的生命周期都将在其存储被重用时结束。不调用析构函数是否会导致资源泄漏或其他不好的事情发生,完全是用户代码问题。
  • @Charles:我想是的,我试图指出,除了提问者想要的之外,如果他的班级是 POD,他也不需要新的安置。
  • 除非他想使用非默认构造函数来初始化对象,这通常很方便。
【解决方案3】:

placement new 的目的是允许您在连续的内存空间中创建对象池或将多个对象对齐在一起,就像使用 std::vector 一样。

如果对象是 C 结构,那么您不需要放置 new 来执行此操作,您可以简单地使用基于 sizeof(struct Foo) 分配内存的 C 方法,其中 Foo 是结构名称,如果您分配多个对象,您可能需要将大小倍增到边界以进行对齐。

但是不需要在那里放置新的对象,你可以简单地把它们memcpy进去。

【讨论】:

    【解决方案4】:

    要回答您的主要问题,是的,您仍然需要调用析构函数,因为必须发生其他事情。

    这个想法在任何生产编译器的实践中都行不通吗?

    你该死的确定你的 C++ 对象适合 C 对象的大小。

    这在技术上是否违反 C++ 语言规范?

    不,但并非所有符合规范的东西都能如你所愿。

    【讨论】:

    • 为什么需要析构函数调用?
    • @Charles:即使构造函数现在什么都不做。如果其他人更改结构以包含需要调用其析构函数的成员(即使其成为非 POD),那么您将面临一大堆问题。您现在编写的代码不应依赖于当前实现的工件,该工件可能不适用于软件的未来版本(尤其是如果代码分布在多个位置)。
    • @Martin York:我并不否认这通常是“一个坏主意”,我只是在提示 Noah Roberts 澄清它是严格要求还是只是不好的做法。
    • @Martin:不可能是非POD,他在包装一个C数据结构:)
    【解决方案5】:

    我了解放置新调用通常与对析构函数的显式调用相匹配。如果我不需要析构函数(没有代码放在那里,也没有具有析构函数的成员变量)我可以安全地跳过显式析构函数调用吗?

    是的。如果我不需要在发布此答案之前飞往纽约,我可以安全地跳过这次旅行吗? :) 但是,如果析构函数真的不需要,因为它什么都不做,那么调用它有什么害处?

    如果编译器可以确定析构函数应该是空操作,我希望它可以消除该调用。如果你不写一个显式的 dtor,请记住你的类仍然有一个 dtor,有趣的案例——这里——是它是否是语言所说的琐碎。

    解决方案:在构建之前销毁先前构建的对象,即使您认为 dtor 是空操作。

    我想为 C API 编写 C++ 绑定。在 C API 中,许多对象只能通过指针访问。而不是创建一个包含单个指针的包装对象(这很浪费并且在语义上令人困惑)。我想用placement new在C对象的地址处构造一个对象。

    这是布局兼容类和 reinterpret_cast 的目的。包括一个静态断言(例如 Boost 的宏、0x static_assert 等)检查大小和/或对齐,如您所愿,以进行简短的完整性检查,但最终您必须了解您的实现如何布置类。如果需要,大多数都提供 pragma(或其他特定于实现的机制)来控制它。

    最简单的方法是在 C++ 类型中包含 C 结构:

    // in C header
    typedef struct {
      int n;
      //...
    } C_A;
    C_A* C_get_a();
    

    // your C++
    struct A {
      void blah(int n) {
        _data.num += n;
      }
    
      // convenience functions
      static A* from(C_A *p) {
        return reinterpret_cast<A*>(p);
      }
      static A const* from(C_A const *p) {
        return reinterpret_cast<A const*>(p);
      }
    
    private:
      C_A _data;  // the only data member
    };
    
    void example() {
      A *p = A::from(C_get_a());
      p->blah(42);
    }
    

    我喜欢将此类转换封装起来,而不是在整个过程中散布 reinterpret_cast,并且更统一(即比较 const 和非 const 的调用站点),因此方便函数。在不注意到仍然必须支持这种类型的使用的情况下修改类也有点困难。

    根据具体的类,您可以公开数据成员。

    【讨论】:

      【解决方案6】:

      第一个问题是:为什么不直接使用演员表?那么就没有放置 new 做任何事情的问题,显然也没有使用析构函数失败的问题。如果 C 和 C++ 类型的布局兼容,结果将起作用。

      第二个问题是:有什么意义?如果你没有虚函数,你没有使用构造函数或析构函数,那么 C++ 类似乎并没有比只使用 C 类型提供任何优势:你编写的任何方法都应该是全局函数。

      我能想象的唯一优势是,如果你想隐藏 C 表示,你可以用一个包含所有私有成员的类覆盖 C 对象,并使用方法进行访问。那是你的目的吗? [我认为这是合理的做法]

      【讨论】:

        猜你喜欢
        • 2011-08-02
        • 1970-01-01
        • 2013-05-22
        • 2018-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-02
        • 1970-01-01
        相关资源
        最近更新 更多