holomenicus
holomenicus

Reputation: 161

C++ initialize differently in container vs as local variable

This question came up while I was trying to write an RAII wrapper class for an OpenGL buffer object. The way that OpenGL creates buffer objects is by a call to void glGenBuffers(GLsizei n​, GLuint* buffers​) which "returns n buffer object names in buffers​." Also, note that the object name 0 is special to OpenGL, being a default value "like a NULL pointer."

So my first idea is to create a class buffer_object like

class buffer_object {
public:
    // constructors, destructor
private:
    unsigned int const name;
};

I want two different initialization behaviors:

The problem is I don't fully understand list initialization, value initialization, and default initialization.

About std::array's default constructor, cppreference.com says it uses aggregate initialization, which is a type of list initialization, which value initializes the elements in an array that aren't given in an initialization list. About value initialization, cppreference.com says,

if T is a class type with at least one user-provided constructor of any kind, the default constructor is called

So, does that mean there's no way to do the following?

{
    buffer_object obj; // has some name given by glGenBuffers; ready to use

    int const n = 3;
    std::array<buffer_object, n> foo; // each element has name 0
    glGenBuffers(n, foo.data()); // gives each element its own name
}
// everything destructed using buffer_object::~buffer_object

— because I'm asking for two different behaviors from the default constructor?

Maybe I'm taking the wrong approach.

Upvotes: 0

Views: 188

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473282

C++ object initialization is not, and cannot be, predicated on where that object is created (with the exception of default-initializing objects of trivial types, which zero-initialize when used for global variables, but not for automatics).

If you give a type a non-trivial default constructor, you are making a strong statement that objects of this type need to have some real code executed before they can be considered valid objects of this type. C++ will therefore assume you are serious about this statement and ensure that said "real code" is executed in all cases when objects of that type are created.

If you give buffer_object a default constructor that generates a buffer object, you are saying that objects of this type should always own a valid OpenGL buffer object (except maybe when they are moved-from). If you give buffer_object a default constructor that zeros out the internal buffer object handle, you are saying that having no OpenGL buffer object handle is a normal, valid state for a buffer_object to be in.

There's no half-way with these two statements. Either the default is empty or it isn't.

Now, you can have alternate constructors. The default constructor could make the handle zero, with another constructor that takes a simple tag type that explicitly says that it will generate a handle. Or better yet, how about a factory function that makes buffer_objects that have generated handles:

buffer_object gen_buffer()
{
  GLuint handle = 0;
  glGenBuffers(1, &handle);
  return buffer_object(handle);
};

auto obj = gen_buffer(); //Clearly states what is going on.

Now there is no question what is in obj: it has a generated buffer object handle. Because that's what the code says is going on.

Upvotes: 1

Related Questions