1

Is there a pattern to automatically call a destructor of a placement-new initialized object on the stack when it exits scope? I want to skip the need to memorize to call the destructor explicitly. Or, is there a different method than the placement-new to construct a stack based object with a variable size data[] tail? I use 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;
}
curiousguy
  • 8,038
  • 2
  • 40
  • 58
Konrad Eisele
  • 3,088
  • 20
  • 35
  • 1
    I think the first problem is that `v` is not necessarily correctly aligned for `aclass`. – melpomene Oct 06 '18 at 17:38
  • 4
    Actually the first problem is that `char v[sizeof(aclass) + size];` is not standard C++ because `size` is not a constant. – melpomene Oct 06 '18 at 17:38
  • I'm not 100% sure, but I wouldn't be surprised if your attempt to make `data` variably sized this way is still *undefined behavior*, since you have defined it to have a static size 0 (so accessing any element of this array would be out of bounds) – UnholySheep Oct 06 '18 at 17:46
  • data[0] is a common idiom when it comes to gcc and C. – Konrad Eisele Oct 06 '18 at 17:48
  • It's definitely possible to use "scope-based resource management" (sometimes called "resource acquisition is initialization") to arrange for an automatic call of the destructor. Doing this will cover all exits from the scope, both normal and abnormal. – Ben Voigt Oct 06 '18 at 17:48
  • @KonradEisele: It's also forbidden by C++. See https://stackoverflow.com/q/4412749/103167 – Ben Voigt Oct 06 '18 at 17:49
  • @BenVoigt: I know that it is unsave, my question is less about use data[0] and more about the destuctor part. Can you elaborate more on "scope-based resource management?" Of course if you can show me a better pattern to avoid data[0] for stack based allocation please share. Maybe templating? – Konrad Eisele Oct 06 '18 at 17:53
  • @geza's answer is an example of SBRM. That is an example of a fully custom class; others might prefer to use `std::unique_ptr` with a custom deleter. – Ben Voigt Oct 06 '18 at 17:57
  • Could the size be really big? If not, then maybe a better solution would be to have a fixed size buffer. Maybe using a `std::stack` for the data might be a possible solution. – Phil1970 Oct 06 '18 at 18:21
  • @UnholySheep An array of size 0 in a struct is a GNU C construct; it doesn't mean that the array has no element; that means a compiler that replicates that extension will not try to bound check the accesses. – curiousguy Oct 22 '18 at 17:09

2 Answers2

3

You can create a wrapper class like this:

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;
        }
};

Usage:

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

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

Note that you're using a GCC extension, as size is not a compile-time constant.

If it was a compile-time constant, then you could put v into Foo (and size would be a template parameter), so f would be simpler.

geza
  • 28,403
  • 6
  • 61
  • 135
  • Really elegant solution! – Konrad Eisele Oct 06 '18 at 17:57
  • Copy constructor and assignment operator should be deleted to prevent some incorrect usage… but it would be much better to use the solution with template size parameter as it would use only standard C++, would be self contained and safer to use. – Phil1970 Oct 06 '18 at 18:02
  • @Phil1970: thanks, I've added deleted functions. I haven't detailed the solution when `size` is a template parameter, as I think that OP does actually need `size` to be non-const (therefore the problem is already non standard compilant). – geza Oct 06 '18 at 18:12
  • Well, it depends if OP can accept `f<100>()` instead of `f(100)` – Phil1970 Oct 06 '18 at 18:17
  • variable constructor might also be used to make it more generic: ```template stack_placement(void *area, _Args&&... __args) { m_ptr = new(area) T(std::forward<_Args>(__args)...); } ``` – Konrad Eisele Oct 06 '18 at 19:16
  • "_If it was const_" if it was a **compile time integer constant** – curiousguy Oct 22 '18 at 17:12
  • There is no check that `area` is correctly aligned. – curiousguy Oct 22 '18 at 19:00
1

There is already a standard wrapper template class which does this - std::unique_ptr.

Note the caveats in the comments

#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-

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • "`// this is illegal in standard c++`" You can put any other strictly positive constant array size, it will still be illegal: 1) accessing elements outside the declared size of the array at runtime is illegal 2) creating an object in a memory buffer whose size is less than `sizeof` of the class is illegal. There is no constant that would give the program well defined behavior here, unless you choose the exact runtime size of the array. At least by using size 0, you are telling the compiler about your real intent. If the compiler supports array size 0, it means it supports a variable size. – curiousguy Oct 22 '18 at 17:18
  • @curiousguy I agree with you. But it certainly illegal in standard c++. Allowing zero-sized arrays is a compiler extension. For what it's worth, I think the standard is wrong to mandate this, and zero-length arrays should be perfectly legal as they are in C. The "there is no memory in a virtual machine" crowd have too much sway over the language. – Richard Hodges Oct 22 '18 at 18:12
  • There is the issue of alignment for array `v`: there is no guarantee that a `char` array is aligned for anything but `char`, which has no alignment requirement, by definition. – curiousguy Oct 27 '18 at 22:12
  • Should there be a check for `nullptr` in `inplace_deleter` (e.g. if the unique_ptr gets moved somewhere)? – 10762409 Sep 16 '22 at 22:04
  • @10762409 nope: https://en.cppreference.com/w/cpp/memory/unique_ptr/~unique_ptr – Richard Hodges Sep 17 '22 at 12:04