Parameter value optimization?

Started by
17 comments, last by ToohrVyk 15 years, 10 months ago
Hell, why doesn't C++ have constructor inheritance? If so, I could write this:
template <class T> struct _init_ : T{    void* operator new( size_t, T* Ptr )    {        return Ptr;    }    void operator delete( void* )    {    }    void operator delete( void*, T* )    {    }};#define CONSTRUCT( type, ptr, args )    ( (void) new(ptr) _init_<type> args )#define DESTRUCT( type, ptr )            ( delete _init_<type> )class Foo{    Foo( int, int );    ....};....CONSTRUCT( Foo, pFoo, (1,2) );


Is there any way around? Or do I have to write this stupid code?
template <class T> struct _init_{    T Data;    _init_() : Data() {}    template <class A1>        _init_( A1 a1 ) : Data(a1) {}    template <class A1, class A2>        _init_( A1 a1, A2 a2 ) : Data(a1, a2) {}    template <class A1, class A2, class A3>        _init_( A1 a1, A2 a2, A3 a3 ) : Data(a1, a2, a3) {}    ....    void* operator new( size_t, T* Ptr )    {        return Ptr;    }    void operator delete( void* )    {    }    void operator delete( void*, T* )    {    }};


[Edited by - 1hod0afop on July 8, 2008 9:55:25 AM]
Advertisement
The push_back does *a lot* of stuff, including calling custom allocator.

As such, function cannot optimize in-place construction. This is problem of value semantics, and not something that can be solved by compiler.


If you find that this in particular is a problem, the solution is this:
struct Obj {    int a;    char b;    Obj() {}    Obj(int a, char b):a(a), b(b) {}};int main(int argc, char**argv) {	std::vector<Obj> vec;	int last = vec.size();	vec.resize(last+1);	vec[last] = Obj(5, 'b');		return 0;}


It results in the following
	int last = vec.size();	vec.resize(last+1);00401F1C  lea         ecx,[eax+1] 00401F1F  lea         edx,[esp+8] 00401F23  mov         dword ptr [esp+24h],eax 00401F27  call        std::vector<Obj,std::allocator<Obj> >::resize (401130h) 	vec[last] = Obj(5, 'b');00401F2C  mov         eax,dword ptr [esp+0Ch] 00401F30  mov         ecx,5 00401F35  mov         dword ptr [eax],ecx 00401F37  mov         byte ptr [esp+4],62h 00401F3C  mov         ecx,dword ptr [esp+4] 


It should be noted that this is exactly what push_back does, but it allows you to control resizing more accurately. One that is done however, the assignment can be performed directly.
One solution I found so far is this
struct cust_init_tag {};void* operator new( size_t, void* Ptr, cust_init_tag ){	return Ptr;}void operator delete( void*, void*, cust_init_tag ) {}template <class T> struct _destruct_{	T Value;	void operator delete( void* ) {}};template <class T> void destruct( T* p ){	delete reinterpret_cast< _destruct_<T>* >(p);}


And now I can invoke constructors and destructors like normal functions:
struct Foo{	Foo( int, int );	~Foo();...};Foo* pFoo = (Foo*) ::malloc( sizeof (Foo) );new(pFoo, cust_init_tag()) Foo( 1, 2 );destruct( pFoo );::free( pFoo );


But looks messy. Please tell me if there's better way.

And my boss still wants me to implement the CRAZY arraylist. =(
I'll again recommend that you take a look at boost::object_pool. It allows you to do this:

void func() {    boost::object_pool<Foo> pool;    Foo *ptr = pool.construct(1, 2);    // Destructor for Foo is called when the pool goes out of scope}
Quote:Original post by ToohrVyk
In theory, yes. You could write code such as:

void foo(Bar a);int main() { foo(Bar()); }


And no temporary value would be created, skipping the copy construction and instantiating the argument directly in the function.

In practice, what happens is that the push-back function usually looks like:

void push_back(const value_type &t){  if (size() == capacity()) { reserve(capacity() * 2); }  new (this->_data + size()) value_type(t);  ++ this->_size;}


Meaning the calling function cannot know where the object should be instantiated, and thus no optimization can be applied. Of course, some compilers may apply some additional optimizations to their associated SC++L implementations, but I don't think this one happens.


Couldn't it inline the push_back implementation and then delay the object construction until after the desired location is known, constructing in place?
Quote:Original post by Gage64
I'll again recommend that you take a look at boost::object_pool.


I saw it already. It's basically like this:
template <class T0> element_type* construct( T0 ) { ... }template <class T0, class T1> element_type* construct( T0, T1 ) { ... }template <class T0, class T1, class T2> element_type* construct( T0, T1, T2 ) { ... }template <class T0, class T1, class T2, class T3> element_type* construct( T0, T1, T2, T3 ) { ... }......



And my boss doesn't like it. It seems to me not genuine, either.
Anyways, thanks very much for your effort. The boost library gave me a lot of ideas.
Quote:Original post by Zahlman
Couldn't it inline the push_back implementation and then delay the object construction until after the desired location is known, constructing in place?


It can only swap the initialization and the memory test-and-reallocate if it can prove that the two are independent (otherwise, it would risk delaying a constructor call that manipulates the vector or the underlying allocator), which therefore can only happen if the constructor is entirely available. And, if the constructor is simple enough to be managed, it usually means that construct-then-copy isn't costly enough to warrant the optimization effort.

Quote:Original post by DevFred
Isn't C++0x going to solve this issue with move semantics? Hooray, another meaning for the double-ampersand :)
I can't recall how likely this is at the moment. It would be great if it did, and I would hope that it would remove the copy here then.

However, I wouldn't think that too often a constructor call would be cheap but the corresponding copy-constructor isn't. When copying is expensive you can get around that by wrapping your type in a smart pointer and storing that instead, or other times by providing an efficient std::swap specialisation and using that on back() after a push_back() of a default-constructed object.

Op:
Many would frown upon the use of a oxymoron term "arraylist" btw. No kind of container could have the full advantages of both a list and an array, and typically wherever a concocted abomination like this used, people end up suffering with the disadvantages of both.
Lets just say I'm glad you think your boss's idea is CRAZY.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:Original post by iMalc
Many would frown upon the use of a oxymoron term "arraylist" btw. No kind of container could have the full advantages of both a list and an array, and typically wherever a concocted abomination like this used, people end up suffering with the disadvantages of both.


"ArrayList" is not an oxymoron—a list is just an ordered sequence of elements, which is perfectly compatible with the concept of array. Consider for instance the Java standard library, which provides a base List interface implemented by ArrayList and LinkedList classes (each with their own specific performance constraints).

This topic is closed to new replies.

Advertisement