LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 10-12-2011, 08:48 AM   #1
xuancong
LQ Newbie
 
Registered: Jul 2010
Posts: 18

Rep: Reputation: 0
Exclamation C++ experts, how to avoid calling local object's destructor upon return?


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:
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
 
Old 10-12-2011, 09:11 AM   #2
SigTerm
Member
 
Registered: Dec 2009
Distribution: Slackware 12.2
Posts: 379

Rep: Reputation: 234Reputation: 234Reputation: 234
Quote:
Originally Posted by xuancong View Post
I've just learned how to call constructor/destructor without calling allocator/deallocator.
Bad idea (unless you absolutely HAVE to use placement new).

Quote:
Originally Posted by xuancong View Post
My objective is to establish a custom container which contain objects like std::vector but is more efficient.
Also a bad idea. It is a premature optimization. Do not reinvent the wheel, plus std::vector is already almost as simple as it could be.

Quote:
Originally Posted by xuancong View Post
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!
You're mistaken. push_back HAS to recreate the object. Your approach will not work if object stored within vector contains pointers to its own members.
Code:
struct A{
    int a;
    int* b;
    A()
    :a(0), b(&a){
    }
    A(const A& other)
    :a(other.a), b(&a){
    }
    A& operator=(const A& arg){
        a = arg.a;
        return *this;
    }
};
Traditional push_back will work on any object with copy constructor.

Another thing that you're obviously unaware of, is that compiler may optimize your code. Some people actually write functions that return object by value.

Quote:
Originally Posted by xuancong View Post
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.
As far as I know, this is behavior required by language design, and you cannot avoid it using any legal way. You can preallocate new objects within your vector using placement new, but that's it. The only safe way to grab external object and copy it into your vector is to use operator= or copy constructor.

Quote:
Originally Posted by xuancong View Post
There're some other constraints, and in conclusion, I am forced to do it in exactly this way, any way to achieve that?
I do not think so. In my opinion, you're trying to break language in order to fix non-existent problem. The simplest solution for everything would be to allocate (in your memory class) extra memory in advance, and use standard C++ copy constructor/operator= for new objects. To avoid spending time in copy constructor, you could use simple "plain old data" objects that do not require it - copying will be as fast as memcpy. Also instead of guessing that "operator= will be slow", measure program performance using profilers.

Last edited by SigTerm; 10-12-2011 at 09:19 AM.
 
Old 10-12-2011, 09:13 AM   #3
johnsfine
LQ Guru
 
Registered: Dec 2007
Distribution: Centos
Posts: 5,286

Rep: Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197Reputation: 1197
Quote:
Originally Posted by xuancong View Post
The push_back() does not need to recreate the object, but rather allocate space and copy in the object only.
To be close to correct and still have object oriented encapsulation, you need to do that copy with the copy constructor (not the assignment operator), which means you need to allocate the space as a different type, and then use placement new to call the copy constructor.

For all that to be worth anything, you need a decent reason to expect the copy constructor alone to be significantly better than another constructor followed by the assignment operator. Remember the optimizer can suppress a lot of redundant stores that the C++ code seems to specify.

So there are cases where you can get significant benefit doing what I just described (and other tricks) in a roll-your-own replacement for vector. But such cases may be less common than you expect.

Quote:
Originally Posted by xuancong View Post
I've just learned how to call constructor/destructor without calling allocator/deallocator.
I can't see that in your code. Can you explain what you mean.

Quote:
m_data=new char [sizeof(MyClass)*_size];
In many architectures, that code is wrong because it does nothing about alignment. In portable C++ you can't assume any alignment for the buffer allocated that way. In typical versions of malloc for x86, you get 4 byte alignment, which might not be good enough.

Quote:
memcpy(&m_data[size],&in,sizeof(MyClass));
Assuming your contained class can be copied that way (rather than placement new with copy constructor) totally violates any concepts of object oriented encapsulation in your design.

Sometimes I need to push for efficiency to an extreme that requires violating the intent of the language to that extent. There are several basic tricks that help both implement and tag (to avoid creating land mines for maintenance) such violations.
I have an empty class named nothing. When a constructor takes a nothing as a parameter that means it is intentionally failing to create a properly constructed object, in most cases it is leaving the object completely unconstructed while the compiler thinks the object is constructed. It is important that normal looking (especially default) constructors don't do such things.
I have also used move() methods that copy with source invalidation into an object that has been fake constructed. The leaves the source object in the condition you're asking about, that it is no longer valid to call the destructor, but the compiler doesn't know that. I've never needed that for a stack local, so I've never kludged a solution to your exact situation.

Quote:
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.)
There are lots of extra complexities to using a vector of pointers instead of a vector of objects. I don't know enough about your situation to guess which is better.

But your stated reason (performance cost of memory allocation) is flawed. There are clear performance gains possible from that extra level of indirection, and there is a simple solution to the memory allocation overhead.

Create a linked pool of free chunks of the size of your object. Create an inline allocator, which:
1) Checks if the pool is empty and if so calls a non inline method to fill the pool.
2) Delinks and takes the first object from the pool.
The method for refilling the pool should allocated a big (maybe 4000) array of chunks of the right size and link them together.

Never delete the objects, just relink into the pool.

If you amortize the rare calls to the pool filling function over the savings of all the allocate and deallocate operations, it becomes insignificant (assuming total allocation and deallocation is large compared to the high water mark). The memory tied up until end of process execution is a rounded up version of the high water mark. In most processes that is not an issue, but you ought to give some thought to whether it is an issue.

Last edited by johnsfine; 10-12-2011 at 09:56 AM.
 
Old 10-18-2011, 11:42 PM   #4
xuancong
LQ Newbie
 
Registered: Jul 2010
Posts: 18

Original Poster
Rep: Reputation: 0
Thanks all, I've gotten the solution-

Code:
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
}
Quote:
Sometimes, we need to push for efficiency to an extreme that requires violating the intent of the language to that extent.
This makes a lot of sense to me. 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.
 
Old 10-19-2011, 02:13 AM   #5
SigTerm
Member
 
Registered: Dec 2009
Distribution: Slackware 12.2
Posts: 379

Rep: Reputation: 234Reputation: 234Reputation: 234
Quote:
Originally Posted by xuancong View Post
Thanks all, I've gotten the solution-
Your solution is bad and incorrect. You should use "placement new" instead.

Code:
//copied from C++ FAQ
 #include <new>        // Must #include this to use "placement new"
  #include "Fred.h"     // Declaration of class Fred
  
  void someCode()
  {
    char memory[sizeof(Fred)];     // Line #1
    void* place = memory;          // Line #2
  
    Fred* f = new(place) Fred();   // Line #3 (see "DANGER" below)
    // The pointers f and place will be equal
  
    ...
  }
Quote:
Originally Posted by xuancong View Post
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.
  1. You don't need to use separate thread to capture keystrokes. Actually, it is a pretty bad idea, since it adds synchronization complications.
  2. When you need to check if multiple keys are pressed at once, you use DirectInput, XInput or LibSDL.
  3. It is not possible to detect simultaneous keypress of 6 keys for computer keyboard. It will work only in situation when 3 out of those 6 keys are alt, ctrl and shift. Keyboard will "lock up" and beep. If you need more than 3..4 keys pressed at once, you need gamepad or midi keyboard - PC keyboard won't do the trick.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Calling All FreeNX EXPERTS! AudioMechanic Linux - General 1 05-03-2010 05:10 AM
Calling All Computer Experts: Why am I so unlucky? gray53 Linux - Newbie 5 09-10-2009 07:04 PM
Calling tc experts: Would like comments on my queueing setup foobert Linux - Networking 6 12-08-2007 08:17 PM
[SOLVED] Calling all experts... NDISWRAPPER help needed! lpshark Linux - Wireless Networking 14 03-30-2007 01:38 PM
Calling all minimalist debian experts! debnewb Debian 15 02-13-2005 07:46 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 12:32 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration