copy constructor clarification

G

Guest

I've just started writing managed C++ (VS 2005 Beta 2) and I really have not
read a whole lot about the differences between managed and unmanaged C++.

Using the class wizard, I can create a managed class defined like:
ref class MyClass {
public
MyClass(void);
~MyClas(void);
};

I'd like to know whether I should add copy and assignment constructors. The
standard syntax of
MyClass(const MyClass&);
MyClass& operator=(const MyClass&);
doesn't work. (compiler error C3699). Do I replace & with ^ ? I would think
that there's times when the default copy constructor will be insufficient.
Part of my confusion stems from my inability to understand the difference
between new and gcnew. When would I want to use one as opposed to the other?

Thanks for any pointers (no pun intended).
 
G

Guest

--
Kapil Khosla, Visual C++ Team
This posting is provided AS IS with no warranties, and confers no rights


Rob said:
I've just started writing managed C++ (VS 2005 Beta 2) and I really have not
read a whole lot about the differences between managed and unmanaged C++.

Using the class wizard, I can create a managed class defined like:
ref class MyClass {
public
MyClass(void);
~MyClas(void);
};

I'd like to know whether I should add copy and assignment constructors. The
standard syntax of
MyClass(const MyClass&);
MyClass& operator=(const MyClass&);
doesn't work. (compiler error C3699). Do I replace & with ^ ?

The equivalent code would be

ref class MyClass {
public:
MyClass(void);
~MyClass(void);
MyClass(MyClass%);
MyClass% operator=(const MyClass%);
};

Think of the operator equivalence for definitions like this:
& = %
* = ^

Dereferencing a ^ is still done with *, though.



I would think
that there's times when the default copy constructor will be insufficient.
Part of my confusion stems from my inability to understand the difference
between new and gcnew.
When would I want to use one as opposed to the other?

You would use new to allocate memory for a native object on the native heap
and gcnew to allocate memory for a manged object on the managed heap. You
can read more on gcnew on
http://msdn2.microsoft.com/library/te3ecsc8(en-us,vs.80).aspx

Thanks,
Kapil
 
I

Ioannis Vranos

Rob said:
I've just started writing managed C++ (VS 2005 Beta 2)

It is not "managed extensions" but C++/CLI.

and I really have not
read a whole lot about the differences between managed and unmanaged C++.

Using the class wizard, I can create a managed class defined like:
ref class MyClass {
public
MyClass(void);
~MyClas(void);
};

I'd like to know whether I should add copy and assignment constructors. The
standard syntax of
MyClass(const MyClass&);
MyClass& operator=(const MyClass&);
doesn't work. (compiler error C3699). Do I replace & with ^ ?


Tracking references are denoted by % and work for both managed and native types. Also you
can define assignment operator like this, but this will work only for C++, and if you use
that type in another language you will have problems.


The managed assignment operator (that is for managed types only) is defined in C++/CLI as
a static member function with two arguments.


You may download the latest C++/CLI draft (currently 1.12) from here:

http://www.plumhall.com/ecma/index.html


I would think
that there's times when the default copy constructor will be insufficient.
Part of my confusion stems from my inability to understand the difference
between new and gcnew. When would I want to use one as opposed to the other?

Thanks for any pointers (no pun intended).


You may check this:

http://www23.brinkster.com/noicys/cppcli.htm
 
G

Guest

Question below...

Ioannis Vranos said:
Tracking references are denoted by % and work for both managed and native types. Also you
can define assignment operator like this, but this will work only for C++, and if you use
that type in another language you will have problems.


The managed assignment operator (that is for managed types only) is defined in C++/CLI as
a static member function with two arguments.

So are you saying that I shouldn't define it as
MyClass% operator=(const MyClass%);
because of language interoperability problems but rather as what? Is this it?
void operator=(MyClass%, const MyClass%); ?

I will take a look at the 300 page C++/CLI spec, but I feel that Microsoft's
documentation on managed code development in C++ could be a little clearer.

Thanks.
 
I

Ioannis Vranos

Rob said:
So are you saying that I shouldn't define it as
MyClass% operator=(const MyClass%);
because of language interoperability problems but rather as what? Is this it?
void operator=(MyClass%, const MyClass%); ?


Well yes, if you intend to share your code with other languages (e.g. making a managed
dll). Otherwise it is OK.


I will take a look at the 300 page C++/CLI spec, but I feel that Microsoft's
documentation on managed code development in C++ could be a little clearer.


Well, the Microsoft documentation is MSDN, and they have the VS 2005 Beta on line. Check
the following on this subject:


http://msdn2.microsoft.com/library/ds533389(en-us,vs.80).aspx


and what is new in general:

http://msdn2.microsoft.com/library/xey702bw(en-us,vs.80).aspx
 
I

Ioannis Vranos

Rob wrote:

So are you saying that I shouldn't define it as
MyClass% operator=(const MyClass%);
because of language interoperability problems but rather as what? Is this it?
void operator=(MyClass%, const MyClass%); ?


ref class MyClass
{
// ...

public:
static MyClass %operator=(const MyClass %x, const MyClass %y)
{
// ...
}

// ...
};


is an example. They are similar to global function ISO C++ operator overloading. You can
also define them as global functions and member functions as usual, but these will not be
accessible from the other managed languages.
 
G

Guest

Thanks. I was well aware of msdn2.microsoft.com/library, but didn't have the
specific links you provided. I had searched that site for "copy constructor"
but did not find anything. I hadn't yet tried searching for operator=,
although that page wouldn't have shown up for that either. :)
 
G

Guest

Actually, operator= cannot be a static member as you show below. You get
compiler error C2581 followed by C2801. In fact, binary operators such as
operator= cannot have two parameters. The first parameter is implied. See
C2804.

So, I will go back to Kapil's suggested syntax.

-Rob
 
I

Ioannis Vranos

Rob said:
Actually, operator= cannot be a static member as you show below. You get
compiler error C2581 followed by C2801. In fact, binary operators such as
operator= cannot have two parameters. The first parameter is implied. See
C2804.


C++/CLI is currently in draft and has not been finished yet. VC++ 2005 is still under
development and not everything has not been implemented yet. *So far* in the draft
C++/CLI, things are as I have described them.

Beta 1 did not support other things, but now does. And the current Beta also does not
support all current C++/CLI draft features.

Do not rely completely on the current Beta to learn C++/CLI. Let's wait C++/CLI to be
finished itself first. :)
 
G

Guest

That's fine from the perspective of learning C++/CLI, but I'm trying to use
VS 2005 Beta 2 to write code and I would've thought that correct assignment
operator syntax would've been put into the compiler kind of early on.

Otherwise, what's the "workaround"? Only create ref classes that can be
shallow copied? I posted a separate question on the syntax I need since
making the changes as suggested by Kapil leaves a problem with member
property assignment.

-Rob
 
I

Ioannis Vranos

Rob said:
That's fine from the perspective of learning C++/CLI, but I'm trying to use
VS 2005 Beta 2 to write code and I would've thought that correct assignment
operator syntax would've been put into the compiler kind of early on.


I think this should be avoided for anything else than just getting a feeling of the
upcoming C++/CLI.
 
G

Guest

Well that's too bad. The Microsoft people I've spoken with claimed that I
could be pretty confident in using it to write code 1) that works and 2) that
wouldn't need to be rewritten when this thing finally ships.
 
I

Ioannis Vranos

Rob said:
Well that's too bad. The Microsoft people I've spoken with claimed that I
could be pretty confident in using it to write code 1) that works and 2) that
wouldn't need to be rewritten when this thing finally ships.


I do not think any GUI code of Beta 2 will compile in the final product out of the box.
For a start, consider the Dispose() definition inside a Form that is produced by the
Designer. In C++/CLI there is no Dispose() definition, the constructor is used for this.
 
T

Tamas Demjen

Ioannis said:
I do not think any GUI code of Beta 2 will compile in the final product
out of the box. For a start, consider the Dispose() definition inside a
Form that is produced by the Designer. In C++/CLI there is no Dispose()
definition, the constructor is used for this.

Beta 1 didn't handle Dispose correctly and the GUI designer generated
the wrong code. In fact, Beta 1 couldn't compile even a trivial GUI
application, because the designer generated the wrong C++/CLI code. It
didn't use the delete operator, which it should have done. Fortunately
this is fixed in Beta 2, and it's absoltely correct now, as far as I can
tell (I'm not on the C++/CLI standard team, so don't take my words for
granted, and the standard is not finalized yet, so you can't say
anything for sure).

Note that there are two Dispose functions, Dispose(void) and
Dispose(bool). It's true that you can not have a Dispose(void) function
in C++/CLI, and you must use the destructor instead. However, if I'm not
mistaken, you can still have a Dispose(bool) function, which is different.

Dispose(void) is what we know as the destructor, and it is supposed to
be called by the user of every class inherited from IDisposable. In
C++/CLI, it means the delete operator, in other .NET languages
(including MC++ and C#) its the Dispose() call:

object = new MyObject(); or object = gcnew MyObject;
[...] [...]
object.Dispose(); delete object;

If you happen to forget about calling Dispose, or there is an exception
before you have a chance to do so, then the garbage collector
automatically calls the finalizer. The finalizer is slightly different
than Dipose(void), because Dispose(void) is supposed to close everything
that needs to be closed, while the finalizer must only deallocate
unmanaged members. For a more accurate description, consult other
sources (books, msdn). I'm not going to describe the exact differences
here, as some experts have already done a very good job with that elsewhere.

There is, however, a *major* difference between the finalizer and
Dispose(void). On the other hand, in many cases the two things are the
same, or at least very similar. Therefore it makes sense to introduce
another function, which does the cleanup, and is called from both the
finalizer and Dispose(void). This way you don't have to duplicate your
code. So instead of writing your deallocation code twice, you write it
once and put it in a function called Dispose(bool). It's unfortunate
that it has the same name, and I find it confusing too, but this is how
Microsoft did it. This common Dispose has a different signature, it has
a "bool disposing" argument, determines whether Dispose(bool) was called
from Dispose(void) (disposing = true) or from the finalizer (disposing =
false). And this Dispose(bool) is still a valid function in C++/CLI, if
I'm not mistaken.

Tom
 
I

Ioannis Vranos

Tamas said:
Note that there are two Dispose functions, Dispose(void) and
Dispose(bool). It's true that you can not have a Dispose(void) function
in C++/CLI, and you must use the destructor instead. However, if I'm not
mistaken, you can still have a Dispose(bool) function, which is different.

Dispose(void) is what we know as the destructor, and it is supposed to
be called by the user of every class inherited from IDisposable. In
C++/CLI, it means the delete operator, in other .NET languages
(including MC++ and C#) its the Dispose() call:

object = new MyObject(); or object = gcnew MyObject;
[...] [...]
object.Dispose(); delete object;

If you happen to forget about calling Dispose, or there is an exception
before you have a chance to do so, then the garbage collector
automatically calls the finalizer. The finalizer is slightly different
than Dipose(void), because Dispose(void) is supposed to close everything
that needs to be closed, while the finalizer must only deallocate
unmanaged members.


I just checked MSDN and found this:

http://msdn2.microsoft.com/library/fs2xkftw(en-us,vs.80).aspx


It seems you are right. However I have got a head ache. :)


In C++/CLI we do have both destructors ~SomeClass() and finalizers !SomeClass(). The way I
view it is that we can simply do:


class SomeClass
{
bool isCleaned;

// ...

public:

// ...

~SomeClass()
{
// Clean up

isCleaned= true;
}

!SomeClass()
{
if(!isCleaned)
// Clean up (unmanaged resources only)
}
};



Where does Dispose(bool) come into picture when we have both destructors and finalizers?



For a more accurate description, consult other
sources (books, msdn). I'm not going to describe the exact differences
here, as some experts have already done a very good job with that
elsewhere.

There is, however, a *major* difference between the finalizer and
Dispose(void). On the other hand, in many cases the two things are the
same, or at least very similar. Therefore it makes sense to introduce
another function, which does the cleanup, and is called from both the
finalizer and Dispose(void). This way you don't have to duplicate your
code. So instead of writing your deallocation code twice, you write it
once and put it in a function called Dispose(bool). It's unfortunate
that it has the same name, and I find it confusing too, but this is how
Microsoft did it. This common Dispose has a different signature, it has
a "bool disposing" argument, determines whether Dispose(bool) was called
from Dispose(void) (disposing = true) or from the finalizer (disposing =
false). And this Dispose(bool) is still a valid function in C++/CLI, if
I'm not mistaken.


OK, but I suppose this Dispose(bool) is not mandatory and does not affect the use of
classes from other languages. As far as I can understand, if someone wants a common
unmanaged resources clean, he can simply do:


class SomeClass
{
bool isCleaned;

// ...

void CleanUnmanagedItems()
{
// ...
}


public:

// ...

~SomeClass()
{
// Possible clean up of managed resources
// ...

CleanUnmanagedItems();

isCleaned= true;
}

!SomeClass()
{
if(!isCleaned)
CleanUnmanagedItems();
}
};
 
T

Tamas Demjen

Ioannis said:
In C++/CLI we do have both destructors ~SomeClass() and finalizers
!SomeClass(). The way I view it is that we can simply do:


class SomeClass
{
bool isCleaned;

// ...

public:

// ...

~SomeClass()
{
// Clean up

isCleaned= true;
}

!SomeClass()
{
if(!isCleaned)
// Clean up (unmanaged resources only)
}
};



Where does Dispose(bool) come into picture when we have both destructors
and finalizers?

It comes in this way:

ref class SomeClass
{
public:
~SomeClass() { Dispose(true); }
!SomeClass() { Dispose(false); }
private:
void Dispose(bool disposing)
{
if(disposing) { DeleteManagedResources(); }
DeleteUnmanagedResources();
}
};

Note that the finalizer is only called when the destructor was not
called. The destructor (I think) automatically disables the finalizer.
So either the ~ or the ! is called, but not both.
OK, but I suppose this Dispose(bool) is not mandatory and does not
affect the use of classes from other languages.

Not in your own classes, as far as I know. I'm really not sure, but I
think the Dispose(bool) function is already there in WinForms classes,
so why not use them? You don't have to, of course.

I simply reacted your email in which you claimed that it was illegal
code and wouldn't compile in the final version. I believe it would,
that's all.
class SomeClass
{
bool isCleaned;

// ...

void CleanUnmanagedItems()
{
// ...
}


public:

// ...

~SomeClass()
{
// Possible clean up of managed resources
// ...

CleanUnmanagedItems();

isCleaned= true;
}

!SomeClass()
{
if(!isCleaned)
CleanUnmanagedItems();
}
};

Now this is something that would work equally well, although I don't
think isCleaned is necessary. Again, the finalizer is only called if and
only if the destructor was not called.

Also, the difference between the finalizer and the destructor is not
that the finalizer clears unmanaged resources only. In many cases that's
the situation, but not always. It's more precise to say that the
finalizer should not call any managed destructor or Dispose, because the
garbage collector calls their finalizer automatically.

There's a main difference between deterministic destruction and GC
finalization. When you call the destructor / Dispose(), you are supposed
to delete / Dispose() disposable managed member owned by the class.
However, when the GC calls the finalizer, it does so for every class
independently. If your finalizer calls member object's finalizers, those
member finalizers will be double called, which is not safe. That's the
main difference between the two.

So

class Unmanaged { [...] }

ref class Inner
{
public:
~Inner() { [...] }
!Inner() { [...] }
};

ref class Outer
{
Inner^ inner;
Unmanaged* u;
public:
~Outer() { delete inner; delete u; }
!Outer() { delete u; }
};

Outer^ outer;

When you call delete outer, it's supposed to delete its managed and
unmanaged members. When you forget about deleting outer, the finalizer
will be called for outer and inner independently, therefore Outer's
finalizer shouldn't delete inner.

I'm not sure where in the object hierarchy Dispose(bool) is introduced.
It's also interesting that every class has a finalizer, but only
IDisposable classes have a destructor. The GC knows about the finalizer,
but doesn't know anything about the destructor / Dispose.

Please correct me if I'm wrong.

Tom
 
I

Ioannis Vranos

Tamas said:
Now this is something that would work equally well, although I don't
think isCleaned is necessary. Again, the finalizer is only called if and
only if the destructor was not called.


We are getting in a mess here, however in managed extensions, finalizer is called even if
Dispose() has been called, and we have to determine ourselves if previous clean up has
been done previously via an explicit call to Dispose().


Things change in *C++/CLI* only, with GC::SuppressFinalize() being *implicitly* called in
a destructor, and inheritance from IDisposable being done implicitly when we define a
destructor.


When you call delete outer, it's supposed to delete its managed and
unmanaged members. When you forget about deleting outer, the finalizer
will be called for outer and inner independently, therefore Outer's
finalizer shouldn't delete inner.

I'm not sure where in the object hierarchy Dispose(bool) is introduced.
It's also interesting that every class has a finalizer, but only
IDisposable classes have a destructor. The GC knows about the finalizer,
but doesn't know anything about the destructor / Dispose.



Dispose(bool) is a standardised (recommended) way to define one clean up method for both
situations (instead of repeating code inside destructor and finalizer, or using some other
arbitrary approach).


Please correct me if I'm wrong.



You are explaining things well. :)


I knew all the things you mentioned but I had forgotten Dispose(bool) existence.


I remember now that I had read about it in my first .NET 1.0 book (a book of MS Press) but
having never used Dispose() explicitly all this time, had made me forget all these details.


Something else interesting now. In C++/CLI, apart from missing the managed extensions
pinning ability of whole managed objects, as far as I know managed extensions also
currently provide the possibility of defining a finalizer as a destructor and invoke
deterministic finalization on managed objects having such a destructor/finalizer.

From the same .NET 1.0 book, I remember that enabling deterministic finalisation on any
managed class, just involves defining a destructor. Check this example:


#using <mscorlib.dll>

__gc class ManagedClass
{
public:
~ManagedClass() {}
};



int main()
{
using namespace System;

ManagedClass *pmc= __gc new ManagedClass;

//Deterministic finalisation
delete pmc;
}


So as far as I can understand (please anyone correct if I am wrong), with managed
extensions, in our managed classes we do not need to even define any Dispose(), but we can
finalise (and not just clean up) explicitly! Unless of course, we want our code to be
usable from other .NET languages.
 
I

Ioannis Vranos

Some corrections:





[ Comment removed because I misunderstood/miscorrected the above code sample of mine]
 
T

Tamas Demjen

Ioannis said:
We are getting in a mess here, however in managed extensions, finalizer
is called even if Dispose() has been called, and we have to determine
ourselves if previous clean up has been done previously via an explicit
call to Dispose().

Managed extensions doesn't have the concept of destructors. Dispose
doesn't automatically call SuppressFinalize().
Things change in *C++/CLI* only, with GC::SuppressFinalize() being
*implicitly* called in a destructor, and inheritance from IDisposable
being done implicitly when we define a destructor.

Exactly, that's correct. I thought the same. So this is a major
difference between Dispose and C++/CLI d'tors. Thanks for mentioning this.

From the same .NET 1.0 book, I remember that enabling deterministic
finalisation on any managed class, just involves defining a destructor.

In C++/CLI it's one more step ahead, because you create the object using
stack syntax, which adds exception safety:

ManagedClass mc;
mc.xxx();
// no need to delete

Note that the object is still created on the managed heap, not on the stack.

Tom
 
I

Ioannis Vranos

Tamas said:
Exactly, that's correct. I thought the same. So this is a major
difference between Dispose and C++/CLI d'tors. Thanks for mentioning this.


Well, yes it is a difference in the sense that it is done implicitly along with chaining
calls to Dispose. However with C++/CLI, the destructor is the Dispose() (when other
languages call Dispose() in our objects, the "destructors" are called with all these
implicit features).

In C++/CLI it's one more step ahead, because you create the object using
stack syntax, which adds exception safety:

ManagedClass mc;
mc.xxx();
// no need to delete

Note that the object is still created on the managed heap, not on the
stack.


Yes. However this is about deterministic destruction (clean up/Dispose), while in managed
extensions it is about deterministic finalization! (if I am not mistaken).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top