views:

254

answers:

5

How does one suppress the automatic initialization and destruction of a type? While it is wonderful that T buffer[100] automatically initializes all the elements of buffer, and destroys them when they fall out of scope, this is not the behavior I want.

#include <iostream>

static int created   = 0,
           destroyed = 0;

struct S
{
    S()
    {
        ++created;
    }
    ~S()
    {
        ++destroyed;
    }
};

template <typename T, size_t KCount>
class fixed_vector
{
private:
    T m_buffer[KCount];
public:
    fixed_vector()
    {
        // some way to suppress the automatic initialization of m_buffer
    }

    ~fixed_vector()
    {
        // some way to suppress the automatic destruction of m_buffer
    }
};

int main()
{
    {
        fixed_vector<S, 100> arr;
    }

    std::cout << "Created:\t"   << created   << std::endl;
    std::cout << "Destroyed:\t" << destroyed << std::endl;
    return 0;
}

The output of this program is:

Created:    100
Destroyed:  100

I would like it to be:

Created:    0
Destroyed:  0

My only idea is to make m_buffer some trivially constructed and destructed type like char and then rely on operator[] to wrap the pointer math for me, although this seems like a horribly hacked solution. Another solution would be to use malloc and free, but that gives a level of indirection that I do not want.


The reason why I want this is because I am making a container and I do not want to pay for the initialization overhead of things that I will not use. For example, if my main function was:

int main()
{
    {
        std::vector<S> vec;
        vec.reserve(50);
    }

    std::cout << "Created:\t"   << created   << std::endl;
    std::cout << "Destroyed:\t" << destroyed << std::endl;
    return 0;
}

The output would be correct:

Created:    0
Destroyed:  0
A: 

You can have a look at the way it's done with the STL containers, but I doubt you can spare yourself mallocs and frees

Oblomov
I can spare myself `malloc` and `free` by using a `char m_buffer[KCount * sizeof(T)]` (though obviously with different semantics, since that is not allowed).
Travis Gockel
A: 

This code:

#include <iostream>
#include <vector>
using namespace std;

int created = 0, destroyed = 0;

struct S
{
    S()
    {
        ++created;
    }
    S(const S & s ) {
        ++created;
    }
    ~S()
    {
        ++destroyed;
    }
};

int main()
{
    {
        std::vector<S> vec;
        vec.reserve(50);
    }

    std::cout << "Created:\t"   << created   << std::endl;
    std::cout << "Destroyed:\t" << destroyed << std::endl;
    return 0;
}

has exactly the output you want - I'm not sure what your question is.

anon
he cannot do `vec[1]` afterwards. (lazy construction)
Johannes Schaub - litb
That is correct (as noted in my question), but how does `std::vector` do this? Looking through g++'s implementation, it *seems* like they do a number of `reinterpret_cast`s on a `char[]` to avoid initialization until the last second.
Travis Gockel
@Travis vector creates uninitialised memory and then uses placement new to create the actual objects in it as needed.
anon
+3  A: 

You can create the array as array of chars and then use placement new to create the elements when needed.

template <typename T, size_t KCount>
class Array
{
private:
    char m_buffer[KCount*sizeof(T)]; // TODO make sure it's aligned correctly

    T operator[](int i) {
        return reinterpret_cast<T&>(m_buffer[i*sizeof(T)]);
    }

After re-reading your question it seems that you want a sparse array, this sometimes goes by the name of map ;o) (of course the performance characteristics are different...)

template <typename T, size_t KCount>
class SparseArray {
    std::map<size_t, T> m_map;
public:
    T& operator[](size_t i) {
        if (i > KCount)
            throw "out of bounds";
        return m_map[i];
    }
Motti
I was thinking `reinterpret_cast`, since the semantics make more sense to me. But I was really hoping to avoid this -- is there any way to not have to do this?
Travis Gockel
You're right, `static_cast` is the wrong choice, updating my answer.
Motti
Your first solution is what I am looking for and is basically exactly what I want.
Travis Gockel
Just some notes on this: You want to reinterpret_cast to `T` is not correctly aligned and randomly crashes on some platforms (e.g on sun sparc, iirc). Also notice that you need flags for this likewise (which is essentially what i do in my solution too). Otherwise, you can't decide when to `new` and when not to. The code was omitted for brevity, i suspect. Anyway, if you like how it is, have fun. It's your code after all :)
Johannes Schaub - litb
I actually use `return reinterpret_cast<T*>(m_buffer)[i]` because it looks cleaner. But I forgot about proper alignment - good catch.
Travis Gockel
@Johannes, you're right as usual (on both counts), regarding alignment, I always think of placement new as being used on `new`ed memory which is aligned to all types so I missed it here.
Motti
+4  A: 

You may want to look into boost::optional

template <typename> struct tovoid { typedef void type; };

template <typename T, size_t KCount, typename = void>
struct ArrayStorage {
  typedef T type;
  static T &get(T &t) { return t; }
};

template <typename T, size_t KCount>
struct ArrayStorage<T, KCount, typename tovoid<int T::*>::type> {
  typedef boost::optional<T> type;
  static T &get(boost::optional<T> &t) {
    if(!t) t = boost::in_place();
    return *t;
  }
};

template <typename T, size_t KCount>
class Array
{
public:
    T &operator[](std::ptrdiff_t i) {
      return ArrayStorage<T, KCount>::get(m_buffer_[i]);
    }

    T const &operator[](std::ptrdiff_t i) const {
      return ArrayStorage<T, KCount>::get(m_buffer_[i]);
    }

    mutable typename ArrayStorage<T, KCount>::type m_buffer_[KCount];
};

A specialization is done for class type that wraps them into an optional, thus calling the constructor/destructor lazily. For non-class types, we don't need that wrapping. Not wrapping them means we can treat &a[0] as a contiguous memory area and pass that address to C functions that want an array. boost::in_place will create the class types in-place, without using a temporary T or its copy constructor.

Not using inheritance or private members allow the class to stay an aggregate, allowing a convenient form of initialization

// only two strings are constructed
Array<std::string, 10> array = { a, b };
Johannes Schaub - litb
This was actually the first thing I looked into. However, a `boost::optional<T>` has an extra flag to give it the ability to use the `operator bool()`, so a `lazy_array<int>` would needlessly take up double the space it needs.
Travis Gockel
@Travis either you go for speed, or you go for space. I don't think this can be solved without a flag. How are you going to see whether `m_buffer[i]` isn't constructed yet? The element placed into that slot can estabish any possible combination of bits, so you can't determine that status by pure means of that memory cell. But really, it's onyly one `bool` it taks. If you go for space, you can allocate an array of `T*` and initialize it to null pointers, and then use `new` to allocate your elements. However i'm not even sure that this will use less space behind the scenes for heap management.
Johannes Schaub - litb
@Johannes: I guess I could have specified in my original question, but in this case I get the best of both worlds since it is a container. On `push_back`, I can treat the element as uninitialized and `pop_back` I can treat the element as initialized. So everything `[0, size())` is initialized and `[size(), KCount)` is unititialized.
Travis Gockel
@Travis, i see now. The flag isn't needed for non-class types! Well let's look to fix it.
Johannes Schaub - litb
@Travis well then `std::vector` seems the right way. `resize` and then `push_back` and the constructor will be called.
Johannes Schaub - litb
A: 

If you want to be like vector, you should do something like this:

template <typename T>
class my_vector
{
    T* ptr; // this is your "array"
    // ...
    public:

    void reserve(size_t n)
    {
        // allocate memory without initializing, you could as well use malloc() here
        ptr = ::operator new (n*sizeof(T)); 
    }

    ~my_vector()
    {
        ::operator delete(ptr); // and this is how you free your memory
    }

    void set_element(size_t at, const T& element = T())
    {
        // initializes single element
        new (&ptr[at]) T(element); // placement new, copies the passed element at the specified position in the array
    }

    void destroy_element(size_t at)
    {
        ptr[at].~T(); // explicitly call destructor
    }
};

This code is obviously for demonstration only, I have omitted my_vector's copy-constructor and any tracking on what's created and what not (calling destructor on a location you haven't called constructor for is probably undefined behavior). Also, STL's vector allocations and deallocations are abstracted away through the use of allocators (the second template argument of vector).

Hope that helps

sbk