简体   繁体   中英

Can I set a member variable before constructor call?

I started to implement an ID based memory pool, where every element has an id, which is basically an index in a vector. In this special case I know the index before I construct the object itself so I thought I set the ID before I call the constructor.

Some details

Allocating an object from an ID based pool is the following:

  1. allocate a free id from the pool
  2. get a memory address based on the id value
  3. construct the object on the memory address
  4. set the ID member of the object

and the deallocation is based on that id

here is the code (thanks jrok):

#include <new>
#include <iostream>

struct X
{
  X()
  {
    // id come from "nothing"
    std::cout << "X constructed with id: " << id << std::endl;
  }
  int id;
};

int main()
{
    void* buf = operator new(sizeof(X));

    // can I set the ID before the constructor call
    ((X*)buf)->id = 42;
    new (buf) X;
    std::cout << ((X*)buf)->id;
}

EDIT

I found a stock solution for this in boost sandbox: sandbox Boost.Tokenmap

Can I set a member variable before constructor call?

No, but you can make a base class with ID that sets ID within its constructor (and throws exception if ID can't be allocated, for example). Derive from that class, and at the moment derived class enter constructor, ID will be already set. You could also manage id generation within another class - either within some kind of global singleton, or you could pass id manager as a first parameter to constructor.

typedef int Id;
class IdObject{
public:
    Id getId() const{
        return id;
    }
protected:
    IdManager* getIdManager() ...
    IdObject()
    :id(0){
        IdManager* manager = getIdManager();
        id = manager->generateId();
        if (!id)
            throw IdException;
        manager->registerId(id, this);           
    }
    ~IdObject(){
        if (id)
            getIdManager()->unregisterId(id, this);
    }       
private:
    Id id;
    IdObject& operator=(IdObject &other){
    }
    IdObject(IdObject &other)
    :id(0){
    }
};

class DerivedObject: public IdObject{
public:
    DerivedObject(){
        //at this point, id is set.
    }
};

This kind of thing.

No, you cannot set anything in an object before its constructor is called. However, you have a couple of choices:

  1. Pass the ID to the constructor itself, so it can store the ID in the object.

  2. Allocate extra memory in front of the object being constructed, store the ID in that extra memory, then have the object access that memory when needed.

If you know the object's to-be address, which is the case for your scenario, then yes you can do that kind of thing. However, it is not well-defined behaviour, so it's most probably not a good idea (and in every case not good design). Although it will probably "work fine".

Using a std::map as suggested in a comment above is cleaner and has no "ifs" and "whens" of UB attached.

Despite writing to a known memory address will probably be "working fine", an object doesn't exist before the constructor is run, so using any of its members is bad mojo.
Anything is possible. No compiler will likely do any such thing, but the compiler might for example memset the object's storage with zero before running the constructor, so even if you don't set your ID field, it's still overwritten. You have no way of knowing, since what you're doing is undefined.

Yes, you can do what you're doing, but it's really not a good idea. According to the standard, your code invokes Undefined Behaviour :

3.8 Object lifetime [basic.life]

The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor . [ Note: initialization by a trivial copy/move constructor is non-trivial initialization. — end note ] The lifetime of an object of type T begins when:

storage with the proper alignment and size for type T is obtained, and

if the object has non-trivial initialization, its initialization is complete .

The lifetime of an object of type T ends when:

— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or

— the storage which the object occupies is reused or released.

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways . For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage (3.7.4.2), and using the pointer as if the pointer were of type void*, is well-defined. Such a pointer may be dereferenced but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:

the pointer is used to access a non-static data member or call a non-static member function of the object

When your code invokes Undefined Behaviour, the implementation is allowed to do anything it wants to. In most cases nothing will happen - and if you're lucky your compiler will warn you - but occasionally the result will be unexpectedly catastrophic.

You describe a pool of N objects of the same type, using a contiguous array as the underlying storage. Note that in this scenario you do not need to store an integer ID for each allocated object - if you have a pointer to the allocated object, you can derive the ID from the offset of the object within the array like so:

struct Object
{
};

const int COUNT = 5; // allow enough storage for COUNT objects

char storage[sizeof(Object) * COUNT];

// interpret the storage as an array of Object
Object* pool = static_cast<Object*>(static_cast<void*>(storage));

Object* p = pool + 3; // get a pointer to the third slot in the pool
int id = p - pool; // find the ID '3' for the third slot

Is there a reason you want to do this before the constructor call?

Allocating an object from an ID based pool is the following:

 1) allocate a free id from the pool 2) get a memory address based on the id value 3) construct the object on the memory address 4) set the ID member of the object and the deallocation is based on that id 

According to your steps, you are setting the ID after the constructor.

so I thought I set the ID before I call the constructor.

I hate to be blunt, but you need to have a better reason than that to wade into the undefined behaviour territory. Remember, as programmers, there is a lot we're learning all the time and unless there is absolutely no way around it, we need to stay away from minefields, undefined behavior being one of them.

As other people have pointed out, yes you can do it, but that's like saying you can do rm -rf / as root. Doesn't mean you should :)

C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, you blow away your whole leg! — Bjarne Stroustrup

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM