Click here to Skip to main content
Click here to Skip to main content

Faster C++ Operators

, 21 Apr 2010
Rate this:
Please Sign up or sign in to vote.
A method to eliminate redundant memory copying in C++ operators

Introduction

This article presents a method to eliminate redundant memory copying in C++ operators that return a new object instance.

Background

One thing that has always annoyed me about the operators in C++ classes is the propensity for generating redundant memory copying. This problem can become severe if the instance contains a lot of data, such as a big matrix.

Let's say you have a MyType operator+ ( const MyType& other ) in your class. The operator is required to return an instance of MyType, which is easy, You just declare one in the function body, initialize it, and return it.

But.. when you return that instance, it goes out of scope because it's local, so C++ has to copy the data that you already initialized with a call to the copy constructor. Your object has to be constructed again!
This is the redundant memory copy I refer to.

Finding a Solution

To circumvent this copying, your operator can return an instance as it's being constructed, like so:

return MyType( SomeParameter ); 

That's sweet, now you got rid of the copy construction call, and you're on your merry way.. unless your class needs lots of data to initialize itself.

Let's say your class is a matrix class (simplified here):

class Mat8x8 {
    double  M[ 8*8 ];
public:
    Mat8x8()                      { memset( M, 0, 64*sizeof(double) ); }
    Mat8x8( const Mat8x8& other ) { memcpy( M, other.M, 512 ); }

    double& operator() ( int row, int col ) { return M[ row*8 + col ]; }

    Mat8x8 operator + ( const Mat8x8& other );
};

Your operator+ has to add each of the elements in the right hand instance to the left hand instance, and return an initialized instance with the result. You think about it for a bit, and figure out you can calculate the result in a temp array and use a constructor that takes your array as input.

Mat8x8::Mat8x8( const double* data )  { memcpy( M, data, 512 ); }

Mat8x8::Mat8x8 operator + ( const Mat8x8& other ) {
    double Sum[64];
    for( int i=0; i<64; ++i )
        Sum[i] = M[i] + other.M[i];
    return Mat8x8( Sum );
    }

Well, at least you don't have to construct your instance twice, but you're not past the copying of initialized data. You build your result in a memory block that has to be copied to the memory C++ decides to use for the object instance. What you really want is a way to build your result straight into the memory the compiler decides to use for your instance. But how can you do that? You can't compute the result because you don't know where to put it, and you can't get somewhere to put it because you don't have anything to pass to the c'tor.

Some people try to get around this by defining a placement 'new' for the class, so they can get the c'tor to build in the memory they want, but that will, of course, only work for dynamically allocated instances. There has to be some other way. Which brings us to 'My way'..
A good old function pointer!

The following code listing embodies the method, and should speak for itself. A special constructor takes a function pointer and pointers to the left hand and right hand operands. The callback function does the actual work of the operator, once C++ has decided where to put your object instance.

class Mat8x8 {
    //
    // The callback function does the actual work for the operator.
    //
    typedef void (*PFnInitMat)( Mat8x8& Mat, void* pLHS, void* pRHS );
    //
    // The special constructor takes a function pointer
    // and pointers to the left hand and right hand operands.
    //
    Mat8x8( PFnInitMat Init, void* pLHS, void* pRHS );
public:
    union {
    double  M[8][8];
    double  A[ 64 ];
    };
    Mat8x8()                      { memset( A, 0, 64*sizeof(double) ); }
    Mat8x8( const Mat8x8& other ) { memcpy( A, other.A, 512 ); }

    Mat8x8  operator + ( const Mat8x8& rhs );
    Mat8x8  operator / ( double rhs );
    // : etc..
};

typedef Mat8x8* PMat8x8;
typedef double* pdouble;

// The special constructor just forwards the work to the callback function.
Mat8x8::Mat8x8( PFnInitMat Init, void* pLHS, void* pRHS )
{
    Init( *this, pLHS, pRHS );
}

// An "operation" callback to add two matrices.
void AddMat88( Mat8x8& Mat, void* pLHS, void* pRHS )
{
    // These two references are not necessary, but makes reading easier.
    Mat8x8& lhs = (Mat8x8&) *PMat8x8( pLHS );
    Mat8x8& rhs = (Mat8x8&) *PMat8x8( pRHS );

    for( int r=0; r < 8; ++r )
        for( int c=0; c < 8; ++c )
            Mat.M[r][c] = lhs.M[r][c] + rhs.M[r][c];
}

// An "operation" callback to divide a matrix by a scalar.
void DivMat88scalar( Mat8x8& Mat, void* pLHS, void* pRHS )
{
    for( int i=0; i < 64; ++i )
        Mat.A[i] = PMat8x8( pLHS )->A[i] / *pdouble( pRHS );
}

// The operators delegate the work via a function pointer.
Mat8x8 Mat8x8::operator + ( const Mat8x8& rhs )
{
    return Mat8x8( AddMat88, this, (void*)&rhs );
}

Mat8x8 Mat8x8::operator / ( double rhs )
{
    return Mat8x8( DivMat88scalar, this, (pdouble)&rhs );
}

Eureka!

No more moving of initialized data. All you pass are a few pointers. Wink | ;)
In your operator, you just call the special constructor, and pass it a function pointer and pointers to the left hand and right hand instances. C++ finds a block for your instance and calls your initializer, which now can build the result straight into the memory the compiler chose.

All the operators can use the same technique, you just write suitable operation functions that you can pass to the constructor.

Afterthoughts

You may think this a lot of doing to get rid of a little data transferring, but what if you're dealing with big matrices of tens, or hundreds, of kBytes? Or you're dealing with massive amounts of instances? Little things add up. I hope you will find this technique useful, and that your programs will get faster for it..
I will certainly keep using 'My way'.

Using the Code

To make this scheme typesafe, you can change the callback function to take typed pointers or references instead of void pointers, and use several different callback function types to handle different operand types. I just used void* for this example - they make programming flexible (under great responsibility).

On the other hand, you could have a common callback function signature that you use for many classes, and cast them as you want.. the choice is yours. The special constructor should definitely not be public though.

Points of Interest

It's a well known, and somewhat sad, fact that you cannot call virtual methods in C++ constructors. You could, however, use this same technique to solve some cases where your constructor cannot know in advance how to deal with some initializations.

History

  • Original publication, April 2010

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Love Nystrom
Software Developer (Senior) Self Employed
Thailand Thailand
Finally retired after a multi-faceted career as a software developer, systems architect, and IT consultant. I've moved away from Europe, and now I live a quiet life in Thailand.
 
I've developed code for all levels of firm/software on IBM PC compatible machines. BIOS code, device drivers, hardware test routines, application framework libraries, relational databases, windows multimedia applications, and more.
 
Now I can pursue my private pet projects, some of which will likely show up here on the code project..

Comments and Discussions

 
GeneralMy vote of 2 PinmemberAescleal17-May-10 11:48 
GeneralRe: My vote of 2 PinmemberLove Nystrom12-Jun-10 6:17 
GeneralRe: My vote of 2 [modified] PinmemberAescleal12-Jun-10 7:52 
Questionc'tor means ? Pinmembermerrykid12-May-10 17:52 
AnswerRe: c'tor means ? PinmemberLove Nystrom14-May-10 2:50 
GeneralGood job! PinmemberStefan6326-Apr-10 22:53 
GeneralRe: Good job! PinmemberLove Nystrom29-Apr-10 4:57 
GeneralUse VC 2010... Pinmemberare_all_nicks_taken_or_what26-Apr-10 1:20 
GeneralRe: Use VC 2010... PinmemberLove Nystrom26-Apr-10 5:25 
GeneralRe: Use VC 2010... Pinmemberwtwhite26-Apr-10 21:19 
GeneralRe: Use VC 2010... PinmemberLove Nystrom29-Apr-10 5:16 
GeneralRe: Use VC 2010... Pinmemberdpisarciuc3-May-10 5:06 
QuestionR-Value operators ? PinmemberAlexandre GRANVAUD22-Apr-10 3:10 
GeneralNRVO and RVO PinmemberKevin Drzycimski21-Apr-10 23:36 
GeneralRe: NRVO and RVO PinmemberNemanja Trifunovic22-Apr-10 3:12 
GeneralRe: NRVO and RVO PinmemberLove Nystrom23-Apr-10 8:54 
GeneralRe: NRVO and RVO PinmemberEjaz26-Apr-10 18:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 21 Apr 2010
Article Copyright 2010 by Love Nystrom
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid