views:

157

answers:

8

Hi all,

I've got a lightweight templated class that contains a couple of member objects that are very rarely used, and so I'd like to avoid calling their constructors and destructors except in the rare cases when I actually use them.

To do that, I "declare" them in my class like this:

template <class K, class V> class MyClass
{
public:
   MyClass() : wereConstructorsCalled(false) {/* empty */}
   ~MyClass() {if (wereConstructorsCalled) MyCallPlacementDestructorsFunc();}

   [...]

private:
   bool wereConstructorsCalled;
   mutable char keyBuf[sizeof(K)];
   mutable char valBuf[sizeof(V)];
};

... and then I use placement new and placement delete to set up and tear down the objects only when I actually need to do so.

Reading the C++ FAQ it said that when using placement new, I need to be careful that the placement is properly aligned, or I would run into trouble.

My question is, will the keyBuf and valBuf arrays be properly aligned in all cases, or is there some extra step I need to take to make sure they will be aligned properly? (if so, a non-platform-dependent step would be preferable)

+2  A: 

Maybe I didn't understand your question, but can't you just do char *keyBuf[..size..];, set it initially to NULL (not allocated) and allocate it the first time you need it?

What you're trying to do with placement new seems risky business and bad coding style.

Anyway, code alignment is implementation dependent.

Andreas Bonini
I'd prefer not to do a heap allocation, since a heap allocation could potentially fail, and then I'd have to come up with some way to deal with that failure.
Jeremy Friesner
Placement new **is a heap allocation** and can fail as well.
Andreas Bonini
How do you figure, Koper? AFAICT the heap is not involved, only the destination array that I specify (which is on the stack)
Jeremy Friesner
Your placement new returns a pointer to stuff on the stack..? I have years of experience in C++ and even time critical code and let me tell you this: you're asking for trouble! Did you at least profile before doing this "optimization" to see if this is a time critical part of your program?
Andreas Bonini
I'm not sure why you want to allocate memory for those infrequently-used objects for every single instance of the class. It seems to me that dynamically allocating the member objects on the handful of occasions that they're used is the better approach.
Anon.
Sure, it returns a pointer to a stack location, because that's where I told new to place the objects: in the keyBuf and valBuf arrays that are in my MyClass object that is on the stack.. Part of my goal is to learn about placement new and how it works, so if I'm in for trouble, I'd love to hear how/why.
Jeremy Friesner
Anon -- these are short-lived iterator objects, so I don't expect there to be more than a few of them in memory at one time.
Jeremy Friesner
Joe Mabel
Re: remark above, placement new shouldn't ever involve a heap allocation, except insofar as the constructor instantiates some other object on the heap (not good practice, that).
Joe Mabel
You know, your claim of "years of experience in C++" gets undermined a bit by not being familiar with placement new. What he's trying to do is perfectly safe and portable, if he's a bit careful.
jalf
+2  A: 

May I ask why you want to place them into a char buffer? Why not just create pointer objects of K and V then instantiate it when you need it.

wheaties
Yes, that is an option, but I'd prefer not to do any dynamic allocation, since the dynamic allocation might (theoretically, anyway) fail and/or take an arbitrary amount of time to complete.
Jeremy Friesner
A: 

If you want to change code alignment use pragma pack

#pragma pack(push,x)

// class code here

#pragma pack(pop) // to restore original pack value

if x is 1, there will be no padding between your elements.

Heres a link to read

http://www.cplusplus.com/forum/general/14659/

TP
This will **severely** impact performance.. And he's doing all of this for performance reasons, so..
Andreas Bonini
Also it's not standard nor portable and it looks like he wants to be from his original question
Andreas Bonini
Looks like #pragma is a non-standard (specifically Microsoft) extension? It would be nicer if I didn't have to use any platform-specific features...
Jeremy Friesner
Couldnt he put the correct value there to ensure correct alignment, not necessarily 1.
TP
i have been using #pragma on linux machines as well and it works pretty good.
TP
#pragma is implementation defined; every compiler chooses which #parma's to accept and what they do. GCC doesn't support #pragma pack, for example. Also like I said packing the struct will kill the performance of your class, don't do it, especially in the name of an "optimization"!
Andreas Bonini
It looks like #pragma pack does the opposite of what I wanted, anyway.... what I wanted is for the two arrays to be word-aligned, and pack would try to pack them as closely as possible instead.
Jeremy Friesner
A: 

I found this answer posted by SiCrane at http://www.gamedev.net/community/forums/topic.asp?topic%5Fid=455233 :

However, for static allocations, it's less wasteful to declare the memory block in a union with other types. Then the memory block will be guaranteed to be aligned to the alignment of the most restrictive type in the union. It's still pretty ugly either way.

Sounds like a union might do the trick!

Jeremy Friesner
It'll work, but you have to take care of of finding an appropriate type to store in the union. It has to have no ctor/dtor (or it won't be legal to store it in a union), and it has to have at least as strict alignment requirements as your own type.
jalf
A: 

To make a very very long story very very short this isn't going to help your performance any and will cause lots of headaches and it won't be long before you get sucked into writing your own memory managemer.

Placement new is fine for a POD (but won't save you anything) but if you have a constructor at all then it's not going to work at all.

You also can't depend on the value of your boolean variable if you use placement new.

Placement new has uses but not really for this.

Charles Eli Cheese
Can you explain why it's not going to work? From what I've read, std::vector does essentially the same thing, and nobody asserts that std::vector is broken.
Jeremy Friesner
Ha, well I assert std::vector is broken. Just look at its source code.The (valid) reason to call placement new is to call constructors and destructors without actually allocating anything. That part is standard and unavoidable if you want to have containers and want to have constructors.The business with using a variable to keep track won't work, though. So you allocate this memory, anything can be in there. If the constructor didn't get called how did the boolean variable get initialized? Now, you could set it outside when you create it, but what does that gain you?
Charles Eli Cheese
If that's the case you are doing all the tracking outside the class anyway, so constructors are pointless. And sometimes for special cases that's better. But, if this is generlized code say for a container class it is going to implode very quickly as the basic problem is hard enough to do with good performance and stability without adding ten times the complication on top of that.
Charles Eli Cheese
Also, the C++ faq is very broken for that matter, the frequently questioned answers site is much more entertaining and turns out to be more informative as well. A big example is saying that placement new is placing an object at a location. You already allocated this memory, and you are calling the constructor, or anyway in any sane scheme that's the case. Any memory management that tried to do as worded would be very broken (see comments on vector).
Charles Eli Cheese
The boolean gets initialized by the MyClass constructor, as shown in the posted code. MyClass is allocated in the normal way; it's not part of the placement-new classes. As for what it gains me, it keeps track of whether the keyBuf and valBuf memory regions have been initialized or not -- something I would need to do in any case, since MyClass needs to behave differently based on whether there is valid data there or not.
Jeremy Friesner
This makes no sense. Why should the bool be at all affected by *another* member being initialized with placement new? Why shouldn't placement new work with non-POD types? And for that matter, how the *hell* is the FQA more informative than the FAQ? Entertaining, perhaps, but some of us have this thing about preferring *accuracy* in our technical resources. Your answer is not it. -1
jalf
The point of placement new is to call a constructor. The bool is not initialized because no contructor is called. In short, you are a moron, Jalf.
Charles Eli Cheese
Oh, and the fqa is FAR more accurate and gives a more reasonable look at things. Though I don't agree with everything in it.
Charles Eli Cheese
MyClass() : wereConstructorsCalled(false) {/* empty */} <-- where the bool is initialized
Jeremy Friesner
A: 

I recommend that you look at the boost::optional template. It does what you need, even if you can't use it you should probably look at its implementation.

It uses alignment_of and type_with_alignment for its alignment calculations and guarantees.

Charles Bailey
A: 

Why are you allocating arrays that are the same size as the templated objects?
IOW, why not just put those as members?

template <class K, class V> class MyClass
{
public:
   MyClass() : wereConstructorsCalled(false) {/* empty */}
   ~MyClass() {if (wereConstructorsCalled) MyCallPlacementDestructorsFunc();}

   [...]

private:
   bool wereConstructorsCalled;
   mutable K keyBuf;
   mutable V valBuf;
};

If you need to access the keyBuf and valBuf as bytes you can always use an unsigned char pointer:

unsigned char * p_keyBuf = (unsigned char *)(&keyBuf);

On the other hand, if these members are used infrequently consider making them smart pointers and dynamically allocating them as necessary:

private:
  mutable boost::shared_ptr<K> pKeyBuf;
  mutable boost::shared_ptr<V> pValBuf;

Click here for more info on Boost Smart Pointers

Thomas Matthews
Putting the templated objects in as plain old members would work; but it would mean that their ctors would be called every time a MyClass object was created, and their dtors would be called every time a MyClass object was destroyed -- even though 99.99% of the time those objects would never be actually used. With placement new/delete I can avoid that overhead. (Admittedly that overhead is likely to be negligible in most cases, but since they are templated types, there's no guarantee that their ctor/dtor overhead will be negligible in all cases)
Jeremy Friesner
Oh and... I'm trying to avoid doing any dynamic allocation in this class, since that would limit the environments in which it could be used (e.g. you aren't allowed to do a dynamic allocation in a real time thread)
Jeremy Friesner
+1  A: 

There's no guarantee that you'll get the appropriate alignment. Arrays are in general only guaranteed to be aligned for the member type. A char array is aligned for storage of char.

The one exception is that char and unsigned char arrays allocated with new are given maximum alignment, so that you can store arbitrary types into them. But this guarantee doesn't apply in your case as you're avoiding heap allocation.

TR1 and C++0x add some very helpful types though:

std::alignment_of and std::aligned_storage together give you a portable (and functioning) answer.

std::alignment_of<T>::value gives you the alignment required for a type T. std::aligned_storage<A, S>::type gives you a POD type with alignment A and size S. That means that you can safely write your object into a variable of type std::aligned_storage<A, S>::type.

(In TR1, the namespace is std::tr1, rather than just std)

jalf
What about the union-with-aligned-type trick I posted a link to above? Any reason why that wouldn't force the compiler to align the array?
Jeremy Friesner
No, that should work too, assuming you can think of another type with the same or stricter alignment requirement. You're not allowed to use types that have constructors or destructors in a union, so you have to be a bit careful.
jalf