I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Let's take the questions one at a time.

  1. Consider the following code:

    
    
    class B 
    
    
    {
    
    
    public:
    
    
      virtual ~B();
    
    
      void operator delete  ( void*, size_t ) throw();
    
    
      void operator delete[]( void*, size_t ) throw();
    
    
      void f( void*, size_t ) throw();
    
    
    };
    
    
    
    
    
    class D : public B
    
    
    {
    
    
    public:
    
    
      void operator delete  ( void* ) throw();
    
    
      void operator delete[]( void* ) throw();
    
    
    };
    
    
    

    Why do B's operators delete have a second parameter, whereas D's do not?

    The answer is: It's just preference, that's all. Both are usual deallocation functions, not placement deletes. (For those keeping score at home, see section 3.7.3.2/2 in the C++ standard.)

    However, there's also a memory error lurking in this underbrush. Both classes provide an operator delete() and operator delete[](), without providing the corresponding operator new() and operator new[](). This is extremely dangerous, because the default operator new() and operator new[]() are unlikely to do the right thing. (For example, consider what happens if a further-derived class provides its own operator new() or operator new[]() functions.)

    Guideline

    graphics/guideline_icon.gif

    Always provide both class-specific new (or new[]) and class-specific delete (or delete[]) if you provide either.


    And the second part of the question: Do you see any way to improve the function declarations?

    All flavors of operator new() and operator delete() are always static functions, even if they're not declared static. Although C++ doesn't force you to say "static" explicitly when you declare your own, it's better to do so anyway, because it serves as a reminder to yourself as you're writing the code and as a reminder to the next programmer who has to maintain it.

    Guideline

    graphics/guideline_icon.gif

    Always explicitly declare operator new() and operator delete() as static functions. They are never nonstatic member functions.


  2. Continuing with the same piece of code: Which operator delete() is called for each of the following delete expressions? Why, and with what parameters?

    
    
    D* pd1 = new D; 
    
    
    delete pd1;
    
    
    

    This calls D::operator delete(void*).

    
    
    B* pb1 = new D; 
    
    
    delete pb1;
    
    
    

    This also calls D::operator delete(void*). Since B's destructor is virtual, of course D's destructor is properly called, but the fact that B's destructor is virtual also implicitly means that D::operator delete() must be called, even though B::operator delete() is not (in fact, cannot be) virtual.

    As an aside to those who are interested in how compilers implement these things: The usual method is that the code actually generated for every destructor is given an invisible "when done destroying the object, should I delete it?" flag that is set appropriately (false when destroying an automatic object, true when destroying a dynamic object). The last thing the generated destructor code does is check the flag and, if it's true, call the correct operator delete().[1] This technique automatically ensures the correct behavior, namely that operator delete() appears to act "virtually" even though it is a static function and, therefore, cannot be virtual.

    [1] The correct variant techniques for array-delete operations are left as an exercise for the reader.

    
    
    D* pd2 = new D[10]; 
    
    
    delete[] pd2;
    
    
    

    This calls D::operator delete[](void*).

    
    
    B* pb2 = new D[10]; 
    
    
    delete[] pb2;
    
    
    

    This is undefined behavior. The language requires that the static type of the pointer that is passed to operator delete[]() must be the same as its dynamic type. For more information on this topic, see also Scott Meyers' section, "Never Treat Arrays Polymorphically" in Meyers99.

    Guideline

    graphics/guideline_icon.gif

    Never treat arrays polymorphically.


    Guideline

    graphics/guideline_icon.gif

    Prefer using vector<> or deque<> instead of arrays.


  3. Are the following two assignments legal?

    
    
    typedef void (B::*PMF)(void*, size_t); 
    
    
    PMF p1 = &B::f;
    
    
     PMF p2 = &B::operator delete;
    
    
    

    The first assignment is fine; we're simply assigning the address of a member function to a pointer to member function.

    The second assignment is illegal because void operator delete( void*, size_t ) throw() is not a nonstatic member function of B, even though as it's written, it may look like one. The trick here is to remember that operator new() and operator delete() are always static members, even if they're not explicitly declared static. It's a good habit to always declare them static, just to make sure that the fact is obvious to all programmers reading through your code.

    Guideline

    graphics/guideline_icon.gif

    Always explicitly declare operator new() and operator delete() as static functions. They are never nonstatic member functions.


  4. Are there any memory-related errors or issues in the following code?

    Short answer: Yes, in every case. Some of them are just a little more subtle than others, that's all.

    
    
    class X 
    
    
    {
    
    
    public:
    
    
      void* operator new( size_t s, int )
    
    
                      throw( bad_alloc )
    
    
      {
    
    
        return ::operator new( s );
    
    
      }
    
    
    };
    
    
    

This invites a memory leak, because no corresponding placement delete exists. Similarly below:



class SharedMemory 


{


public:


  static void* Allocate( size_t s )


  {


    return OsSpecificSharedMemAllocation( s );


  }


  static void  Deallocate( void* p, int i )


  {


    OsSpecificSharedMemDeallocation( p, i );


  }


};


class Y


{


public:


  void* operator new(size_t s,


                     SharedMemory&m ) throw( bad_alloc )


  {


    return m.Allocate( s );


  }


This invites a memory leak, because no operator delete() matches this signature. If an exception is thrown during construction of an object to be located in memory allocated by this function, the memory will not be properly freed. For example, consider the following code:



SharedMemory shared; 


...


new (shared) Y; // if Y::Y() throws, memory is leaked


Further, any memory allocated by this operator new() cannot safely be deleted because the class does not provide a usual operator delete(). This means that a base or derived class's operator delete(), or the global one, will have to try to deal with this deallocation (almost certainly unsuccessfully, unless you also replace all such surrounding operator delete's, which would be onerous and evil).



  void operator delete( void* p, 


                        SharedMemory& m,


                        int i ) throw()


  {


    m.Deallocate( p, i );


  }


};


This Y::operator delete() is useless because it can never be called.



void operator delete( void* p ) throw() 


{


  SharedMemory::Deallocate( p );


}


This is a serious error, because the replacement global operator delete() is going to delete memory allocated normally by the default ::operator new(), not by SharedMemory::Allocate(). The best you can hope for is a quick core dump. Evil.



void operator delete( void* p, 


                      std::nothrow_t& ) throw()


{


  SharedMemory::Deallocate( p );


}


The same comment applies again here, but this time it's slightly more subtle. This replacement operator delete() will be called only if an expression like "new (nothrow) T" fails because T's constructor exits with an exception and will try to deallocate memory not allocated by SharedMemory::Allocate(). Evil and insidious.

Guideline

graphics/guideline_icon.gif

Always provide both class-specific new (or new[]) and class-specific delete (or delete[]) if you provide either.


If you got and understood all of these answers, then you're definitely on your way to becoming an expert in memory-management mechanics.

    I l@ve RuBoard Previous Section Next Section