【问题标题】:destructor on placement-new放置新的析构函数
【发布时间】:2018-10-06 17:31:14
【问题描述】:

当退出作用域时,是否有一种模式可以自动调用堆栈上的 placement-new 初始化对象的析构函数?我想跳过记住显式调用析构函数的需要。或者,是否有与 placement-new 不同的方法来构造具有可变大小 data[] 尾部的基于堆栈的对象?我用 g++。

/* g++ f.cpp -o f.exe */
/* 8< --- f.cpp ----  */
#include <stdio.h>
#include <stdlib.h> 
#include <string>
class aclass {
public:
    aclass(int size) : size_(size) {};
    ~aclass() { /* do something */ };
    int size_;
    char data[0];
};

void f(int size)
{
    char v[sizeof(aclass) + size];
    aclass *p = new(static_cast<void*>(&v)) aclass(size);
    p->~aclass();
}

int main(int argc, char **argv)
{
    f(10);
    f(100);
    return 0;
}

【问题讨论】:

  • 我认为第一个问题是v 不一定与aclass 正确对齐。
  • 其实第一个问题是char v[sizeof(aclass) + size];不是标准C++,因为size不是常量。
  • 我不是 100% 确定,但如果您尝试以这种方式改变 data 的大小仍然是未定义的行为,我不会感到惊讶,因为您有将其定义为静态大小 0(因此访问此数组的任何元素都将超出范围)
  • data[0] 是 gcc 和 C 的常见习语。
  • 绝对可以使用“基于范围的资源管理”(有时称为“资源获取即初始化”)来安排析构函数的自动调用。这样做将涵盖范围内的所有退出,包括正常和异常。

标签: c++ gcc g++ destructor placement-new


【解决方案1】:

你可以像这样创建一个包装类:

template <typename T>
class Foo {
    private:
        T *m_ptr;
    public:
        Foo(void *area, int size) {
            m_ptr = new(area) T(size);
        }
        Foo(const Foo &) = delete;
        ~Foo() {
            m_ptr->~T();
        }

        void operator=(const Foo &) = delete;

        T *operator->() {
            return m_ptr;
        }
};

用法:

void f(int size) {
    char v[sizeof(aclass)+size];
    Foo<aclass> p(v, size);

    p->doSomething(); // call a function from aclass
}

请注意,您使用的是 GCC 扩展,因为 size 不是编译时常量。

如果它是一个编译时常量,那么您可以将v 放入Foo(而size 将是一个模板参数),所以f 会更简单。

【讨论】:

  • 真正优雅的解决方案!
  • 复制构造函数和赋值运算符应该被删除以防止一些不正确的使用......但是使用带有模板大小参数的解决方案会更好,因为它只使用标准C++,将是自包含的并且更安全使用。
  • @Phil1970:谢谢,我添加了已删除的功能。当size 是模板参数时,我没有详细说明解决方案,因为我认为OP 实际上确实需要size 是非常量(因此问题已经是非标准编译器)。
  • 好吧,这取决于 OP 是否可以接受 f&lt;100&gt;() 而不是 f(100)
  • 变量构造函数也可以用来使其更通用:template&lt;typename... _Args&gt; stack_placement(void *area, _Args&amp;&amp;... __args) { m_ptr = new(area) T(std::forward&lt;_Args&gt;(__args)...); }
【解决方案2】:

已经有一个标准的包装模板类可以做到这一点 - std::unique_ptr

注意 cmets 中的注意事项

#include <cstdio>
#include <cstdlib> 
#include <string>
#include <memory>

class aclass {
public:
    aclass(int size);
    ~aclass();
    int size_;
    char data[0];   // this is illegal in standard c++
};

// deleter which only calls destructor, does not deallocate
struct inplace_deleter
{
    template<class T>void operator()(T* p) const noexcept
    {
        p->~T();
    }
};

void f(int size)
{
    // VLAs are not standard c++. This is a compiler extension on gcc.
    char v[sizeof(aclass) + size];

    using ptr_type = std::unique_ptr<aclass, inplace_deleter>;
    auto p = ptr_type(new(&v) aclass(size));

    // auto deleted
}

int main()
{
    f(10);
    f(100);
    return 0;
}

https://godbolt.org/z/qEwld-

【讨论】:

  • "// this is illegal in standard c++" 你可以放任何其他严格的正常量数组大小,它仍然是非法的:1)在运行时访问数组声明大小之外的元素是非法的 2)创建一个对象在大小小于类的sizeof 的内存缓冲区中是非法的。除非您选择数组的确切运行时大小,否则此处没有可以为程序提供明确定义的行为的常量。至少通过使用大小 0,您是在告诉编译器您的真实意图。如果编译器支持数组大小为 0,则表示它支持可变大小。
  • @curiousguy 我同意你的看法。但它在标准 C++ 中肯定是非法的。允许大小为零的数组是编译器扩展。对于它的价值,我认为这个标准是错误的,零长度数组应该是完全合法的,因为它们在 C 中是完全合法的。“虚拟机中没有内存”的人群对语言有太多的影响力。
  • 数组v 存在对齐问题:不能保证char 数组与char 以外的任何对象对齐,根据定义,char 没有对齐要求。跨度>
猜你喜欢
  • 2018-01-17
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 2011-05-24
  • 2013-07-24
  • 2013-02-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多