Page 1 of 2

C++ Question

Posted: Sat Feb 26, 2005 7:23 am
by XStream
Hey,

I am looking at C++ at the moment and I have come across a weird problem, I am not sure if it is compiler based, OS based or language based, so I thought I would put it to you guys and see what you come up with.

This code basically explains itself but I thought that the pointer to the instance of X would die and be unusable and cause a crash of some kind, but on windows 200 using borland command line tools, this code functions and the X is printed twioce without a hitch!!

Code: Select all

#include <iostream.h>

class X {
   public:
      X() {}
      ~X() {}
      void Print() {
         cout << "X" << endl;
      }
};

void main() {
   X *xObject = new X;

   xObject -> Print();

   delete xObject;
   xObject = 0;

   xObject -> Print(); <-- Shouldn't this cause a program crash??
}
Cheers. :)

Re:C++ Question

Posted: Sat Feb 26, 2005 10:05 am
by AR
No, this is what happens behind the scenes:

Code: Select all

void Print(X *this) { cout << "X" << endl; }
The this variable is passed in on the stack, since the class has no variables then the pointer to "this" is useless (ie. points to data that is zero bytes long)

if you want to deliberately crash it, then try this:

Code: Select all

class X {
   public:
      X() {}
      ~X() {}
      void Print() {
         cout << "X" << endl;
         i = 1;
      }
      int i;
};
The important thing to note, is that the code only exists once, the only thing that makes the code operate on one particular instance of the class is the this pointer.

Re:C++ Question

Posted: Sat Feb 26, 2005 10:15 am
by Candy
XStream wrote: This code basically explains itself but I thought that the pointer to the instance of X would die and be unusable and cause a crash of some kind, but on windows 200 using borland command line tools, this code functions and the X is printed twioce without a hitch!!
You might want to upgrade. I hear windows 2000 is very stable... Sure you already have that millennium bug patch?

Code: Select all

#include <iostream.h>

class X {
   public:
      X() {}
      ~X() {}
      void Print() {
         cout << "X" << endl;
      }
};

void main() {
   X *xObject = new X;

   xObject -> Print();

   delete xObject;
   xObject = 0;

   xObject -> Print(); <-- Shouldn't this cause a program crash??
}
Answer to your in-code question: Yes. It should not work. Then why does it work?

Your program does not so much dereference the xObject pointer when it calls a function, but only when any function actually uses something stored in the object. Your objects never contain anything (are logically size 0) so it never matters where the pointer points, you will not get a crash since it's never used.

Try making one that prints an instance variable and look at it crash.

Re:C++ Question

Posted: Sat Feb 26, 2005 12:15 pm
by Colonel Kernel
Also, making Print() virtual and calling it via a pointer to a base class would make it crash.

Code: Select all

#include <iostream.h>

class XBase {
public:
    virtual void Print() = 0;
    virtual ~XBase() {}
};

class X : public XBase {
  public:
      X() {}
      ~X() {}
      void Print() {
        cout << "X" << endl;
      }
};

void main() {
  XBase *xObject = new X;

  xObject -> Print();

  delete xObject;
  xObject = 0;

  xObject -> Print(); <-- This will probably crash now...
}
Note that just because it doesn't crash doesn't mean it's a correct program. You're triggering undefined behaviour, which can really be anything... even apparently correct behaviour.

Re:C++ Question

Posted: Sat Feb 26, 2005 5:32 pm
by XStream
Thanks guys, that clears it up for me. :)

Here is another question for you guys, would the code below cause a memory leak, by not destroying pX properly or does delete know how to delete the memory properly.

Code: Select all

X *pX = new X;
void *pvX = (void *)pX;
delete pvX;

Re:C++ Question

Posted: Sat Feb 26, 2005 7:15 pm
by Colonel Kernel
I have no idea what that would do. It's probably undefined behaviour. At best, the memory might get freed, but I doubt the destructor would run. Does it even compile?

Re:C++ Question

Posted: Sat Feb 26, 2005 7:28 pm
by AR
Judging by the structure of delete

Code: Select all

void inline operator delete (void *Address)
It should free the memory since all it needs is the address so the block size should be in the management data. It will most likely not call the destructor though, since "delete class" usually breaks down to "class->~class(); delete(class);", if the compiler thinks it's void, then it won't add the code to call the destructor.

Re:C++ Question

Posted: Sat Feb 26, 2005 9:33 pm
by Schol-R-LEA
Colonel Kernel wrote: Note that just because it doesn't crash doesn't mean it's a correct program. You're triggering undefined behaviour, which can really be anything... even apparently correct behaviour.
While we're on that topic, it should be mentioned that [tt]main()[/tt] should always be declared as [tt]int[/tt], not [tt]void[/tt]. It's a niggling point, and many compilers won't even give a warning on it, but it is the standard and can cause problems if the program is called by a script which tries to check for a bad program termination. Just something to keep in mind.

Re:C++ Question

Posted: Sat Feb 26, 2005 10:32 pm
by XStream
Ok, thanks guys, definately need the destructor which isn't called.

Here is another question, is it possible to have a member function called when a pointer of a class is assigned, as in this:

Code: Select all

X x;
X *y;
y = &x;  What member function could be called here, any??

Re:C++ Question

Posted: Sat Feb 26, 2005 10:35 pm
by AR
None I believe, you're merely creating a pointer

Re:C++ Question

Posted: Sat Feb 26, 2005 10:43 pm
by XStream
So when creating reference counted objects, how would you know, that there is a new reference to the existing object, I know you can add a member function AddRef() but then you must remeber everytime to call it for the reference countng to function correctly.

Thanks. :)

Re:C++ Question

Posted: Sun Feb 27, 2005 12:45 am
by Colonel Kernel
XStream wrote: So when creating reference counted objects, how would you know, that there is a new reference to the existing object, I know you can add a member function AddRef() but then you must remeber everytime to call it for the reference countng to function correctly.

Thanks. :)
Use a smart pointer. Try boost::shared_ptr for starters.

http://www.boost.org/libs/smart_ptr/smart_ptr.htm

Re:C++ Question

Posted: Sun Feb 27, 2005 2:01 am
by XStream
Thanks for that but it doesn't really answer the question, I will put a bit more of an explanation into it.

Lets say I have a CWindow object that represents a GUI window. The window has a caption property that is a CString, in this ficticious program the window's caption must remain the same as the path to a database, the path to the database would be stored in a CString as well, it would be good to only have one CString object and have both the window's caption and database path use it. The problem is shown below:

Code: Select all

// defined elsewhere
class CString;

class CWindow {
public:
    ...
    void SetCaption( CString &sCaption ) {
        m_pCaption = &sCaption;
    }
private:
    CString *m_pCaption;
};

CWindow MainWnd;
CString *pDBPath;

int main() {  // <-- Just for you Schol-R-LEA ;)

pDBPath = new CString("Some Initial Path");

MainWnd.SetCaption( *pDBPath );

... Other Code ...

delete pDBPath;

... Other Code ...

return 0;

}
At the point of the second '... Other Code ...' m_pCaption in MainWnd points to nothing, if at anytime it tries to access it the program will crash, this is because assigning a value to a pointer does not invoke any member or friend functions so the original instance has no way to know that another pointer is referencing it, is adding an AddRef(); member function and calling it after referencing the object the only possible way to handle such a situation.

Code: Select all

class CWindow {
public:
    ...
    void SetCaption( CString &sCaption ) {
        m_pCaption = &sCaption;
        sCaption.AddRef();  // is this my only solution??
    }
private:
    CString *m_pCaption;
};
I know that this is probably a reasonable solution, but I think it is ugly and prone to possible errors which defeat the purpose of reference counting anyway, the whole idea is to automatically release the resources, but only once the reference count is 0, one mistake of not adding the call to AddRef(); breaks the whole thing. >:(

Cheers.

Re:C++ Question

Posted: Sun Feb 27, 2005 2:03 am
by Candy
You can override operator& iirc. however, you can duplicate the pointer without the class knowing, so it's not useful in this case.

Re:C++ Question

Posted: Sun Feb 27, 2005 12:59 pm
by Colonel Kernel
The problem you're trying to solve is exactly the problem solved by boost::shared_ptr. It "remembers" to call "AddRef" and "Release" for you by doing so in its constructor, destructor, copy constructor, and assignment operator (and probably in some other methods that I'm forgetting). The difference is that shared_ptr keeps its own pointer to an external refcount rather than relying on a refcount inside the pointed-to object. This is invisible to you as the user of shared_ptr, and also is invisible to anyone writing classes to be used with shared_ptr. The downside is that there is a bit more overhead (two pointers per shared_ptr instance instead of one, plus having to manage the external refcount).

Here's your example, re-done with boost::shared_ptr:

Code: Select all

// defined elsewhere
class CString;

typedef boost::shared_ptr<CString> CStringPtr;

class CWindow {
public:
    ...
    void SetCaption( CStringPtr sCaption ) {
        m_pCaption = sCaption;
    }
private:
    CStringPtr m_pCaption;
};

CWindow MainWnd;
CStringPtr pDBPath;

int main() {  // <-- Just for you Schol-R-LEA ;)

pDBPath = new CString("Some Initial Path");

MainWnd.SetCaption( pDBPath );

... Other Code ...

// No need to delete pDBPath anymore. It automatically
// deletes the CString object when the shared_ptr's refcount
// reaches 0.

... Other Code ...

return 0;

}
Like most smart pointers, shared_ptr overloads many of the operators that are useful for pointers, like unary * and ->. Syntactically, using a shared_ptr is almost the same as using a real pointer. Just remember to always pass them by value so that the refcount gets updated properly.

If you want more info, see my previous reply. :)