Experts, how to avoid calling local object's destructor?

Programming, for all ages and all languages.
Post Reply
xuancong
Posts: 14
Joined: Fri Jul 02, 2010 9:15 pm

Experts, how to avoid calling local object's destructor?

Post by xuancong »

Hi C++ experts,

I've just learned how to call constructor/destructor without calling allocator/deallocator. My objective is to establish a custom container which contain objects like std::vector but is more efficient. The push_back() does not need to recreate the object, but rather allocate space and copy in the object only. But now the problem comes!

Code: Select all

MyClass{
 int x,y;
 char *buf1,*buf2;
 MyClass(int size){
  buf1 = new int [size];
  //... do some initialization, very slow
 }
 ~MyClass(){
  //...do some clean up before releasing buffers, very slow
  delete [] buf1;
 }
 operator = (MyClass &rhs){
 // very complicated and time consuming
 }
};

MyVector{
 int size, max_size;
 MyClass *m_data;
 MyVector(int _size){
  m_data=new char [sizeof(MyClass)*_size];
  size=0;
  max_size=_size;
 }
 void push_back(MyClass &in){
  //... check for size overflow and relocate space if neccessary
  memcpy(&m_data[size],&in,sizeof(MyClass));
  size++;
 }
}

void func1(){
 MyClass A(100);
 std::vector <MyClass> v;
 v.push_back(A); // object gets recreated by std::vector and assigned, which is slow and unneccessary
} // local object A is deallocated and destroyed, which is slow and unneccessary

void func2(){
 MyClass A(100);
 MyVector <MyClass> v;
 v.push_back(A); // object gets copied in directly, neither constructor/allocator nor assigner gets called, which is fast
} // now the problem comes, how to deallocate local object A without calling its destructor
So the problem is that in func2(), the local object A somehow decides to be added into MyVector, it's contents gets copied into MyVector, internal buffer pointers need to be kept, but when func2() returns, ~A() will be called, causing the A in MyVector to have internal buffer pointers invalid.

Under my specific situation, I have some constraints:
1. I am not going to use vector of pointer because MyClass is going to be created and destroyed frequently, dynamic memory allocation hits performance. In this case, I preallocate space for MyClass, no dynamic memory allocation. (Internal buffers of MyClass is not destroyed everytime, sometimes just gets transferred.)
2. I am not going to increment the std::vector first and then get a pointer to newly created MyClass object in the std::vector to access it, because sometimes the local object A may not need to be pushed into MyVector.
3. I am not going to increment the std::vector first, get the pointer to access new element, then delete it if no need to be inserted because MyVector is in fact a circular buffer accessed by multiple threads, creating temporary object causes problems.

There're some other constraints, and in conclusion, I am forced to do it in exactly this way, any way to achieve that?

Wang Xuancong
SDS
Member
Member
Posts: 64
Joined: Fri Oct 23, 2009 8:45 am
Location: Cambridge, UK

Re: Experts, how to avoid calling local object's destructor?

Post by SDS »

Fundamentally you have a scoping and ownership problem. You have allocated a buffer to A, which owns it. You have then stored the pointer contained in A elsewhere, and destroyed the original A. Both your stored, and original, A variables consider that they 'own' the data pointed to.

You could tell A not to own the data any more.

Code: Select all

MyClass{
    ...
    ~MyClass() {
    //...do some clean up before releasing buffers, very slow
        if (NULL != buf1)
            delete [] buf1;
    }

    void buf_release () {
        buf1 = NULL;
    }
};

void func2(){
    MyClass A(100);
    MyVector <MyClass> v;
    v.push_back(A); 
    A.buf_release();
}
But I still don't think this is a very nice solution. You will need to consider whether each and every copy of A owns the data, and clearing up the vector becomes a pain (especially if at any point you copy 'A' back to a local variable). You will need to consider, at all times, who owns the data.

You could always use some form of reference counting on the pointed-to data. That would solve the problem nicely. Trying to avoid calling the destructor, however, is just ugly.
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re: Experts, how to avoid calling local object's destructor?

Post by Solar »

(One word up front: In that other thread, I said that vector<MyClass>(200) would reserve space for 200 objects. This is wrong: It would default-construct 200 objects. This is possibly my most frequent error in the C++ language, and probably not what you want - my apologies. Use vec.reserve(200) instead.)
xuancong wrote:My objective is to establish a custom container which contain objects like std::vector but is more efficient. The push_back() does not need to recreate the object, but rather allocate space and copy in the object only.

[...]

Code: Select all

 void push_back(MyClass &in){
  //... check for size overflow and relocate space if neccessary
  memcpy(&m_data[size],&in,sizeof(MyClass));
  size++;
 }
You heard of shallow vs. deep copy, and copy constructors, have you? Shoving C++ objects around by memcpy() is asking for trouble. It might even work for your MyClass, but is sure to break with other objects.
1. I am not going to use vector of pointer because MyClass is going to be created and destroyed frequently, dynamic memory allocation hits performance. In this case, I preallocate space for MyClass, no dynamic memory allocation. (Internal buffers of MyClass is not destroyed everytime, sometimes just gets transferred.)
One, most malloc() / new() implementations hold a certain cache of memory in user space that is not immediately returned to the OS upon free() / delete(). While you are right that dynamic memory allocation hurts performance, this might not be as bad as you think. Have you measured the performance? Do you have proof that there will be a performance hit of a magnitude that is worth all this?

Two, have you heard of allocator objects? Such an object can be passed to the constructor of a vector, and can subsequently controll all allocations and de-allocations of objects in that vector. They were added to the STL explicitly for handling costly objects in STL containers. Since I don't really know what you are up to, all I can do is recommending to read up about them, and check if they might solve your specific problem.
2. I am not going to increment the std::vector first and then get a pointer to newly created MyClass object in the std::vector to access it, because sometimes the local object A may not need to be pushed into MyVector.
3. I am not going to increment the std::vector first, get the pointer to access new element, then delete it if no need to be inserted because MyVector is in fact a circular buffer accessed by multiple threads, creating temporary object causes problems.
Huh?
There're some other constraints, and in conclusion, I am forced to do it in exactly this way...
Could it be we're talking some elaborate kind of homework assignment here? If yes, please tell us so.

Otherwise, and to reiterate what I wrote in that other thread with different words: If you are "forced" to do it exactly that way, please tell the retard who came up with that brain-dead implementation of MyClass and those "requirements" for its handling to do it himself.

The problem here is neither a shortcoming of the STL or the vector implementation, the fault sits squat in the middle of the MyClass implementation.
Every good solution is obvious once you've found it.
SDS
Member
Member
Posts: 64
Joined: Fri Oct 23, 2009 8:45 am
Location: Cambridge, UK

Re: Experts, how to avoid calling local object's destructor?

Post by SDS »

Further to that, I would suggest that you look into implementing a copy constructor to either:
  • Implement a deep copy
  • Transfer ownership of relevant allocated data (similar to std::auto_ptr, or preferably std::unique_ptr)
xuancong
Posts: 14
Joined: Fri Jul 02, 2010 9:15 pm

Re: Experts, how to avoid calling local object's destructor?

Post by xuancong »

Thanks all, I've gotten the solution-:)

Code: Select all

void func2(){
 char A_buf[sizeof(MyClass)]; // allocate space on stack
 MyClass &A = *(A*)&A_buf; // let object A and A_buf share the same space
 A.MyClass(100);      // explicit constructor invokation
 MyVector <MyClass> v;
 if(need to push) v.push_back(A); // object gets copied in directly, neither constructor/allocator nor assigner gets called, which is fast
 else  A.~MyClass(); // explicit destructor invokation
 //on-stack allocation will be released automatically upon return
}
Sometimes, we need to push for efficiency to an extreme that requires violating the intent of the language to that extent.
I should try to avoid doing this kind of things in standard programs as much as possible because it violates the principle of object-oriented programming language.
The reason for my doing this is that, I've been writing a DJ game called 'Project Diva PC'. The keystokes are captured by an high-priority isolated thread. At those peak moments involving simultaneous pressing of 6 keys, I observed that the time interval between adjacent keystrokes are typically several tens or hundreds of nanoseconds that I do really need to quickly store the timestamp and keystroke data and return immediately.
Currently, all objects in the CircularQueue are dead structures which makes my queue no difference from std::vector, I'm just thinking 1 step ahead, what if the structures do have their own constructor/destructor/assigner. I hope I will never encounter that case. :D
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re: Experts, how to avoid calling local object's destructor?

Post by Solar »

xuancong wrote:

Code: Select all

void func2(){
 char A_buf[sizeof(MyClass)]; // allocate space on stack
 MyClass &A = *(A*)&A_buf; // let object A and A_buf share the same space
 A.MyClass(100);      // explicit constructor invokation
 MyVector <MyClass> v;
 if(need to push) v.push_back(A); // object gets copied in directly, neither constructor/allocator nor assigner gets called, which is fast
 else  A.~MyClass(); // explicit destructor invokation
 //on-stack allocation will be released automatically upon return
}
Would you mind explaining how this is different, in regards to where memory is allocated and how constructors / destructors are called, from the following code:

Code: Select all

void func2(){
    MyClass A( 100 );  // space gets allocated on stack
    MyVector< MyClass > v;
    if ( need to push ) v.push_back( A );
    // on-stack allocation will be released automatically upon return
}
Because I don't see where your code is more efficient.
Every good solution is obvious once you've found it.
xuancong
Posts: 14
Joined: Fri Jul 02, 2010 9:15 pm

Re: Experts, how to avoid calling local object's destructor?

Post by xuancong »

Solar wrote: Would you mind explaining how this is different, in regards to where memory is allocated and how constructors / destructors are called, from the following code:

Code: Select all

void func2(){
    MyClass A( 100 );  // space gets allocated on stack
    MyVector< MyClass > v;
    if ( need to push ) v.push_back( A );
    // on-stack allocation will be released automatically upon return
}
In both cases, A is allocated on the stack and released from the stack. There's no difference here. But as I said, MyVector only copies in the object, it doesn't duplicate the object as does by std::vector. So if v is std::vector, it's safe, A gets duplicated and the duplicated copy gets into the vector container. But if v is MyVector, A gets copies in, but not duplicated completely (deep copy vs. shallow copy). After the function returns, if the A in the MyVector is accessed elsewhere, it will crash because the internal buffer pointers of A are no longer valid.

The performance gain comes from not duplicating A by reconstruction when pushing into container. If A is a dead structure, there's no difference.
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re: Experts, how to avoid calling local object's destructor?

Post by Solar »

You failed to satisfyingly answer at least three fundamental questions in this thread. They are important - not so much for us, but for you:
  • Do you have proof that std::vector and standard copy-constructing would give you performance problems, or are you doing these optimizations because you think you might get a performance problem?
  • Why can't you use a std::vector<MyClass *> (or similar, smart-pointered version thereof)?
  • Why can't you use an allocator object instead of implementing your own memory management in a copy of std::vector that isn't?
You came into this thread hell-bent on doing it "your way": Implementing your own vector and "avoiding calling local object's destructor". Your "solution" is a construct that does its own memory management in a non-standard and surprising way. You spell that "t-r-o-u-b-l-e".

In my experience, "thinking ahead" like you did does usually lead to bad, not very maintainable code that has lots of issues.

4 out of the 5 times I encountered that particular "code smell" at office work before, this "thinking ahead" was deemed "necessary" because the person in question did not put enough effort into actually bench-testing the standard code before, or was unaware of features of the standard library (like, vector::reserve() and allocator objects). Of those 4 cases, 3 resulted in severe problems in production, including next-to-impossible-to-debug memory faults and customers biting your head off over the phone. Not fun. The fourth occassion luckily turned out to be non-functional before we "went life", and was replaced with std::map and a couple of functions from <algorithm>...

If you break the standard way of doing things, do it with a reason. Not guesswork, not hearsay, not assumptions. Such constructs will come back to haunt you.

That being said, I hope your solution works for you.
Every good solution is obvious once you've found it.
Post Reply