Thread: Crazy Assignment Operator Semantics

  1. #1
    Registered User
    Join Date
    Apr 2007
    Posts
    141

    Crazy Assignment Operator Semantics

    I am creating a matrix class whose underlying operations are being performed on an external device (a high end GPU). the matrix is templated and the declaration looks something like this,

    Code:
    template <int nrows, int ncols, int ld = nrows>
    class cmatrix 
    {
    public:
    	float *val ; // pointer to the data on the device
    /* ...  more stuff */
    
    
    cmatrix &operator=(const cmatrix &rhs)
    {
    	
    	if (this != &rhs) {
    
    /* I do NOT delete the old val pointer and create a new one since the default copy
       constructor always allocates a 'fixed'  size block of memory */
    		
    	/* nasty device code that copies the stuff in rhs.val to this->val */
    		
    
    	
    
    	}
    	
    return(*this)  ;
    } // end operator=
    } ;
    I have an overloaded multiply operator that does what you'd expect. Note that it returns a cmatrix object. Un-optimized code will delete the product object, C, after copying it to the output object.

    Code:
    /* matrix multiplication */
    template <int rows, int cols, int ld,  int cols1, int ld1>
    cmatrix<rows, cols1, rows> operator*(const cmatrix<rows,cols,ld> &A, const cmatrix<cols,cols1,ld1> &B)
    {
    cmatrix<rows,cols1,rows> C ;
    /* call device operations to move A * B into C .... */
    
    return(C) ;
    
    }

    Now comes the strange part. Creating a matrix that is a product of two matrices can create memory errors depending on how I use the assignment operator and whether I making a debug or optimized release code. The issue is which assignment operator is called (default or templated) and how the product operator returns it's values.

    This occurs on MS Visual Studio 2005, within it's debugger.
    Code:
    int main(int argc, char *argv[])
    {
    /* Init code creates 2 2 x2 matrix A and B */
    
    
    /* Default constructor called for C, but template assignment operator NOT used.
    Thus a memory leak and invalid memory created for C */
    cmatrix<2,2>  C = A * B ;   
    
    std::cout << C ;  // ERROR!  C has already been deleted (if using debug)
    
    
    }
    On the other hand if declare C first, the templated assignment operator does get used, but even stranger behavior quickly follows.

    Code:
    int main(int argc, char *argv[])
    {
    /* Init code creates 2 2 x2 matrix A and B */
    
    
    
    cmatrix<2,2>  C  
    /* temp output of A*B deleted after operator*  returns  
    C  = A * B ;  // wtf?  right hand side of assignment operator deleted!,  attempting
    // to delete  temp output twice, causing a call to two frees on the same pointer.
    
    std::cout << C ;  // never  reaches this
    
    }
    So there are two mysteries here. The first is why the default assignment operator gets used in the first case, where I put the declaration and product on the same line. The second mystery is why after the assignment operator it feels the need to delete it's right hand side again!

    I find this business of what the default behavior of the compiler, especially when using templates rather arcane. By the way, the release mode (optimized) code seems to run just fine. So some assignment operator optimizations actually change the semantics. Is this just an MS 'feature'?

  2. #2
    Registered User
    Join Date
    Apr 2007
    Posts
    141

    Wrong Copy Constructor

    I found the main problem with my approach and that was the fact that I did not override the copy constructor. I thought I didn't really need one, but then I realized that the copy constructor is used automatically in the following:


    1) When an object is created from another object of the same type
    2) When an object is passed by value as a parameter to a function
    3) When an object is returned from a function

    I knew of course about 1), but uses 2 and 3 were news to me and of course are the source of my problem. It would be preferable to never have this called and I was hoping extra copying and creation of temp. classes could be held to a minimum. It is likely the optimized code is removing at least one copy constructor call, but not all of them unfortunately.

  3. #3
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    If the question is what can you do about it, I guess you can pass things and return things by reference instead of by copy, which will prevent copying.

  4. #4
    Registered User
    Join Date
    Apr 2007
    Posts
    141
    Yes thats right. What you really want to be able to do is to use arithmetic syntax for matrix operations.

    e.g.
    Z = C * (A+B) + D

    and so forth. If the compiler doesn't help you with some optimizations you end up creating and destroying a bunch of extra temp objects.

    I end up having a love hate relationship with this language. I love it's power and expressiveness, and I just hate all the hidden rules for memory management, class inheritance, template instantiation and in some cases arcane syntax.

  5. #5
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by SevenThunders
    If the compiler doesn't help you with some optimizations you end up creating and destroying a bunch of extra temp objects.
    If you really want to, you can help the compiler along with expression templates.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  6. #6
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    The rule of three: if you have one of copy constructor, copy assignment, or destructor, you really need all three.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  7. #7
    Registered User
    Join Date
    Apr 2007
    Posts
    141
    Thats a really good point. Thanks laserlight. In fact I was aware of this approach and it's pretty powerful. I thought though that I could just do some hand coded optimization in the critical paths, and use my current matrix templating to get me fairly close to hand optimized C.

    I wonder though how hard it would be to adapt their templates to my situation. Actually what would have been even more powerful would have been to use a language like Haskell, which has ridiculous abstract type capabilities and then essentially generate and compile C/C++ code, with my device function calls, at the back end.

    Maybe version 2 can look at this .

  8. #8
    Registered User
    Join Date
    Apr 2007
    Posts
    141
    Quote Originally Posted by CornedBee View Post
    The rule of three: if you have one of copy constructor, copy assignment, or destructor, you really need all three.
    I guess I relearned this the hard way .

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Menu
    By Krush in forum C Programming
    Replies: 17
    Last Post: 09-01-2009, 02:34 AM
  2. Assignment Operator, Memory and Scope
    By SevenThunders in forum C++ Programming
    Replies: 47
    Last Post: 03-31-2008, 06:22 AM
  3. Screwy Linker Error - VC2005
    By Tonto in forum C++ Programming
    Replies: 5
    Last Post: 06-19-2007, 02:39 PM
  4. Help with a pretty big C++ assignment
    By wakestudent988 in forum C++ Programming
    Replies: 1
    Last Post: 10-30-2006, 09:46 PM
  5. Replies: 1
    Last Post: 10-27-2006, 01:21 PM