15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Please let me make a copy

Paul Martinsen -- pmartinsen@hort.cri.nz
Tuesday, December 17, 1996

Environment: VC++ 4.0, Win 95

Hi,

I want to make a copy of an object that I derived from CObject, but 
MFC makes CObject's copy operator private, which means that I can't 
call it from derived classes, even if I overload the copy operator in 
my own class.

Is there anyway to get around this short of copying each member
variable individually (what a great source for bugs). I tried using
memcpy but this didn't work (caused all sorts of runtime errors). 

Any suggestions?
Paul.



Mike Blaszczak -- mikeblas@nwlink.com
Wednesday, December 18, 1996

[Mini-digest: 6 responses]

At 12:43 12/17/96 +1200, Paul Martinsen wrote:
>Environment: VC++ 4.0, Win 95

>I want to make a copy of an object that I derived from CObject, but 
>MFC makes CObject's copy operator private, which means that I can't 
>call it from derived classes, even if I overload the copy operator in 
>my own class.

The CObject copy operator wouldn't be able to do anything for you:
it doesn't know anything about the member variables you have or how
they should be copied.  How _could_ it?

>Is there anyway to get around this short of copying each member
>variable individually


No, there isn't. When you derived your own class from

>(what a great source for bugs).

That's absurd!

>I tried using memcpy but this didn't work (caused all sorts
> of runtime errors). 

You probably have some members of your derived class that, in turn,
own pointers.  You can't simply memcpy() stuff and expect it to work.
Would you try to code this?

        CString str("Hockey is the b
.B ekiM
http://www.nwlink.com/~mikeblas/
I'm afraid I've become some sort of speed freak.
These words are my own. I do not speak on behalf of Microsoft.

-----From: Mike Blaszczak 

At 12:43 12/17/96 +1200, Paul Martinsen wrote:
>Environment: VC++ 4.0, Win 95

>I want to make a copy of an object that I derived from CObject, but 
>MFC makes CObject's copy operator private, which means that I can't 
>call it from derived classes, even if I overload the copy operator in 
>my own class.

The CObject copy operator wouldn't be able to do anything for you:
it doesn't know anything about the member variables you have or how
they should be copied.  How _could_ it?

>Is there anyway to get around this short of copying each member
>variable individually

No, there isn't. When you derived your own class from CObject, you became
responsible for its implementation.  You could put things into your class
that _can't_ be copied, or even replicated, and there's just no way for
CObject to know that.  It can't possibly implement a copy operator that
works for every class that anyone could possibly write. 

>(what a great source for bugs).

That's absurd! Why are so many developers looking to blame their bugs
on somoene else?

>I tried using memcpy but this didn't work (caused all sorts
> of runtime errors). 

You probably have some members of your derived class that, in turn,
own pointers.  You can't simply memcpy() stuff and expect it to work.
Would you try to code this?

        CString str("hockey is the best!");
        CString str2;
        memcpy(&str, &str2, sizeof(CString));
        printf("I think that %s\n", (LPCTSTR) str2);

By trying to use memcpy() yourself, you're breaking all of the semantics
of the "has-a" objects inside of your CObject-derived class.

Your question really has little to do with MFC.  It's really founded in
C++ programming.  One good way to get help with this kind of thing is
to sit down with a good C++ tutorial or textbook.  You might also try
reading Marshall Cline's C++ FAQ.

.B ekiM
http://www.nwlink.com/~mikeblas/
I'm afraid I've become some sort of speed freak.
These words are my own. I do not speak on behalf of Microsoft.

-----From: "Jeffrey Smith" 

>>  Environment: VC++ 4.0, Win 95
>>  Is there anyway to get around this short of copying each member
>>  variable individually (what a great source for bugs). I tried using
>> memcpy but this didn't work (caused all sorts of runtime errors). 

When library providers protect copy ctors and assignment operators, they do it
for a reason. Don't try to circumvent the semantics of what CObject provides. 
Read the
note in afx.h just above these method's decls.

The short answer is, if you need to copy, don't derive from CObject.

-- Jeff WS
-----From: "Ronald D. Patton" 

Paul,

The MasterClass Visual C++ 4 by Wrox Press
addresses this issue. They say that "you should
avoid them because they confuse the user and
don't fit in with MFC's two-phase construction".
And to ensure that you avoid them they make
them private as you have already discovered.
They recommend a constructor that takes no
arguments and a Create() method used to 
initialize any attributes.

Good Luck,
Ron Patton

----------
> From: Paul Martinsen 
> To: mfc-l@netcom.com
> Subject: Please let me make a copy
> Date: Monday, December 16, 1996 6:43 PM
> 
> Environment: VC++ 4.0, Win 95
> 
> Hi,
> 
> I want to make a copy of an object that I derived from CObject, but 
> MFC makes CObject's copy operator private, which means that I can't 
> call it from derived classes, even if I overload the copy operator in 
> my own class.
> 
> Is there anyway to get around this short of copying each member
> variable individually (what a great source for bugs). I tried using
> memcpy but this didn't work (caused all sorts of runtime errors). 
> 
> Any suggestions?
> Paul.
-----From: SCS.007@mch.scn.de

>> Environment: VC++ 4.0, Win 95

>> I want to make a copy of an object that I derived from CObject, but 
>> MFC makes CObject's copy operator private, which means that I can't 
>> call it from derived classes, even if I overload the copy operator in 
>> my own class.

>> Is there anyway to get around this short of copying each member
>> variable individually (what a great source for bugs). I tried using
>> memcpy but this didn't work (caused all sorts of runtime errors). 

>> Any suggestions?
>> Paul.

CObject is there only to provide you functionality such as RTCI, Dynamic 
Creation and Serialization. You need not be bothered about CObject's copy 
c'tor. Just make sure your copy c'tor works fine and the rest will be fine. 

Chandru.
-----From: "Brad Wilson" 

> I want to make a copy of an object that I derived from CObject, but 
> MFC makes CObject's copy operator private, which means that I can't 
> call it from derived classes, even if I overload the copy operator in 
> my own class.

If you look at the definition of CObject (afx.h in mfc4.2b), you'll see
that there is nothing to copy.  Why can't you make your own copy
constructor/opeator = a public and use that instead?  There's no need to
call the base class in this case.

--
Brad Wilson                       Custom software development & solutions
crucial@pobox.com               Internet and intranet consulting services
The Brads' Consulting          Windows NT/Windows 95 software development
http://www.thebrads.com    Web site planning, development and maintenance
   Content (C) 1996 Brad Wilson; no reproduction without authorization




Mike Blaszczak -- mikeblas@nwlink.com
Thursday, December 19, 1996

>-----From: "Jeffrey Smith" 
>The short answer is, if you need to copy, don't derive from CObject.

That's bad advice.

If you need to copy, you can still derive from CObject; you just need
to implement your own operator=() function(s).

Try this on for size:

--- CLIP AND SAVE --- CLIP AND SAVE ---

// Compile with
//    cl /MT thisfile.cpp
// or
//    cl /MTd thisfile.cpp

#include 

class CFoo : public CObject
{
public:
   CFoo(const char* pstr);
   ~CFoo();

   void operator=(const CFoo& refFoo);
   void Show() const;

protected:
   char* m_pstr;
};

CFoo::CFoo(const char* pstr)
{
   printf("Constructor: \"%s\"\n", pstr);
   m_pstr = strdup(pstr);
}

CFoo::~CFoo()
{
   printf("Destructor: \"%s\"\n", m_pstr);
   free(m_pstr);
}

void CFoo::operator=(const CFoo& refFoo)
{
   printf("Copying \"%s\"\n", refFoo.m_pstr);
   if (m_pstr != NULL)
      free(m_pstr);
   m_pstr = strdup(refFoo.m_pstr);
}

void CFoo::Show() const
{
   printf("Showing: \"%s\"\n", m_pstr);
}

void main()
{
   CFoo foo1("Yaya!");
   CFoo foo2("Hoho!");

   foo2 = foo1;

   foo1.Show();
   foo2.Show();
}

--- CLIP AND SAVE --- CLIP AND SAVE ---

.B ekiM
http://www.nwlink.com/~mikeblas/
I'm afraid I've become some sort of speed freak.
These words are my own. I do not speak on behalf of Microsoft.




Mike Blaszczak -- mikeblas@nwlink.com
Saturday, December 21, 1996

At 20:52 12/19/96 -0800, Mike Blaszczak wrote:

Someone pointed out that my operator=() implmentation didn't work
for self-assignment.  That is,

        CFoo fooOne("Leaky");
        fooOne = fooOne;

wouldn't end up getting you into trouble.  Even though my example
was trivial, this kind of problem can spring up in more complicated
situations and leave you wondering what to do.  A fixed 
CFoo::operator=() would probably look like this:

CFoo& CFoo::operator=(const CFoo& refFoo)
{
   if (&refFoo != this)
   {
      printf("Copying \"%s\"\n", refFoo.m_pstr);
      if (m_pstr != NULL)
         free(m_pstr);
      m_pstr = strdup(refFoo.m_pstr);
   }
   return *this;
}

Note that I also snuck in a change to the return type; you'd want
to have a reference to a CFoo returned so that you could string along
your assignments, like the language allows for:

   CFoo fooBestDate;
   CFoo fooBestSport;
   CFoo fooBestHobby;
   CFoo fooHockey("Hockey");
   CFoo fooBreakfast, fooLunch, fooDinner;

   fooBreakfast = fooLunch = fooDinner =
   fooBestDate = fooBestSport = fooBestHobby = fooHockey;

You'll note that CFoo implements a simple, useless string class. A more
interesting and robust string class, like CString, can have a more complicated
operator=() implementation.  CString, for example, uses reference counting
to improve efficiency.  CString isn't CObject-derived, but the principle
the same.

These techniques aren't MFC-specific: they're something you should pick up
from any C++ programming text that pays attention to the design of 
applications in the language.

.B ekiM
http://www.nwlink.com/~mikeblas/
I'm afraid I've become some sort of speed freak.
These words are my own. I do not speak on behalf of Microsoft.




Paul Martinsen -- pmartinsen@hort.cri.nz
Monday, January 06, 1997

Environment: VC++ 4.0, Win 95
> 
> >I want to make a copy of an object that I derived from CObject, but 
> >MFC makes CObject's copy operator private, which means that I can't 
> >call it from derived classes, even if I overload the copy operator in 
> >my own class.
> 
Hmm, looks like my question wasn't quite clear enough, and I seem to 
have antagonized a few people. Sorry about that.

Firstly, I believe the question is related to mfc since I'm talking 
in particular about the way CObject is implemented. I'm sure there 
are very good reasons for the particular implementation, but I also 
think that I have a valid point.

I want to use the default assignment operator (the one that is
generated automatically by the compilier) to do a member by member
copy. While I was reading your replies, I realized that a default
assignment operator won't be created for classes derived from
CObject since they already has an assignment operator (don't suppose
there is anyway around this?) -- the one hidden in CObject. What I
wanted to do was call the base classes assignment operator and have
it call the default operator (which I now realize doesn't exist).

The particular problem that prompted this question was classes that 
hold preference data for an application I'm writing. The reason I 
derived from CObject was so that I could use serialization to store 
preferences when the program closes, and use CObjectArray.

I want to copy the class so that I can do something like:
void CMyView::OnPreferences()
{ CDialogPreferences dlgPrefs;
  // m_Preferences is derived from CObject.
  dlgPrefs.m_Preferences = GetDocument()->m_DocPreferences; 

  if (dlgPrefs.DoModal() == IDOK)
    GetDocument()->m_DocPreferences = dlgPrefs.m_Preferences;
}

So in my case I _want_ the compilier to use the default assignment operator. 
If I have to write my own, adding another option to the preferences 
class means I have to remember to add it to the assignment operator. 
This might not sound like much, but my program is getting quite big 
(at least for me; I'm only a hobbist) and there is quite a lot to 
keep track of. (Actually its not quite that simple. My preferences 
class contains about 1/2 dozen members also derived from CObject: eg 
CDisplayPreference, CEditingPreferences etc. Each corresponds to a 
page in a tab control.)

> >(what a great source for bugs).
> 
> That's absurd! Why are so many developers looking to blame their
> bugs on somoene else?
That's a bit unfair :( The program isn't behaving as it is supposed to 
.: it _is_ a bug, in my case, reasonably obvious and easy to fix. I 
wasn't really trying to blame anybody for anything; I just wanted to 
fix my problem.

> >I tried using memcpy but this didn't work (caused all sorts
> > of runtime errors). 
> 
> You probably have some members of your derived class that, in turn,
> own pointers.  You can't simply memcpy() stuff and expect it to work.
> Would you try to code this?
It was one of those long-shots late one friday night :) When I thought 
about it I did realize that there would be all sorts of problems 
involving the this pointer, and probably the virtual function tables. 
I left it out of my question since I thought the post was getting too 
long (now look how much I've written :) )

> By trying to use memcpy() yourself, you're breaking all of the
> semantics of the "has-a" objects inside of your CObject-derived
> class.
I disagree. Using memcpy is bad because it damages the hidden class 
variables, but _if_ it didn't I think using memcpy (or something 
comparable) inside a member function (in this case operator=(...)
should be ok, because here we are working directly with the 'has-a' 
relationship. Of course this argument may come down to nothing more 
than differing semantics.

> own pointers.  You can't simply memcpy() stuff and expect it to work.
> Would you try to code this?
> 
>         CString str("hockey is the best!");
>         CString str2;
>         memcpy(&str, &str2, sizeof(CString));
>         printf("I think that %s\n", (LPCTSTR) str2);
> 
No, but the folling will work, since the compilier will create a 
assignment operator for CElephant (Note it isn't derived from 
CObject):
class CElephant
{ public:
    CString m_strFirstName, m_strLastName;
};

:
:
void MyFunction(CElephant &e1, const CElephant &e2)
// Not a very useful function.
{ e1 = e2;
}
:
:

Ok, here is where I will probably get myself in trouble again as I 
offer my solution.
Ignoring all the other things that the mfc people had to think of 
when they put the class libraries together, and only considering this 
particular situation (I am really trying to avoid offending anybody 
here), I think that the copy constructor and assignment operator 
should have been left out of CObject. This way, in my situation where 
I want to use CArchive & the serialize routines, I could derive 
from CObject, use these routines and not have to worry about 
assignment operators. I would argue that if there is some reason not 
to make a copy eg, you have a pointer to some memory that is owned by 
the class, you can modify the _default_ C++ behaviour, rather than 
re-implement the default behaviour when it is wanted. I realise one 
reason for the particular implemenation is so that we can't do stupid 
things with derived classes eg:
CView NewView;
// Lets create a new view:
NewView = GetCurrentView(); // this is wrong!!!
But _maybe_ it would have been better to hide the assignment/ copy 
constructor in CWnd or some other decendant of CObject. Without going 
to any detail, I guess you can see that similar problems could arrise 
using the CObjectArray etc classes.

Thanks to all the people who took the trouble to reply to my earlier 
post.
Happy New Year,
Paul Martinsen.
P.S. Actually I think, in general, the MFC classes are GREAT.



Bradley V. Pohl -- brad.pohl@pobox.com
Sunday, January 05, 1997

#pragma tolerance( whining, off )

[Moderator's note: Hmm.  I think I should have ignored the previous
message...]

>Environment: VC++ 4.0, Win 95
>>=20
>> >I want to make a copy of an object that I derived from CObject, but=20
>> >MFC makes CObject's copy operator private, which means that I can't=20
>> >call it from derived classes, even if I overload the copy operator =
in=20
>> >my own class.
>>=20
>Hmm, looks like my question wasn't quite clear enough, and I seem to=20
>have antagonized a few people. Sorry about that.

>Firstly, I believe the question is related to mfc since I'm talking=20
>in particular about the way CObject is implemented. I'm sure there=20
>are very good reasons for the particular implementation, but I also=20
>think that I have a valid point.

Okay, this is getting ridiculous.  I probably shouldn't even bother to =
get involved, but I can't sit back and listen to such drivel.

>I want to copy the class so that I can do something like:
>void CMyView::OnPreferences()
>{ CDialogPreferences dlgPrefs;
>  // m_Preferences is derived from CObject.
>  dlgPrefs.m_Preferences =3D GetDocument()->m_DocPreferences;=20
>
>  if (dlgPrefs.DoModal() =3D=3D IDOK)
>    GetDocument()->m_DocPreferences =3D dlgPrefs.m_Preferences;
>}

Hello, what are you thinking?  This goes to the very heart of C++:  it =
is a _launguage feature_!   By default, a "shallow" assignment operator =
is provided for a class.  There are many cases when shallow copying =
simply won't work.
e.g., if the class has an embedded pointer that is delete'd in the dtor.

In these cases you _must_ provide an override or your code will behave =
eratically at best (at worst?) and blow up at worst (at best?).

Next, realize that, especially in MFC, embedding pointers is quite =
common.
Combined with the fact that CObject has no data (but does have a =
v-table), it was a _great_ design decision to make CObject::operator=3D =
private. =20

This is a _very_ common C++ design idiom.  It catches shallow copy =
"gotchas" before they exist - at compile time.  Furthermore, it places a =
minimal burden on the programmer - all you have to do is write the =
assignment operator.  One assignment statement per member!

>So in my case I _want_ the compilier to use the default assignment =
operator.=20
>If I have to write my own, adding another option to the preferences=20
>class means I have to remember to add it to the assignment operator.=20
>This might not sound like much,=20

Not only does it not sound like much, it ISN'T much!  You seem to have a =
very cavalier attitude about modifying class designs.  Changing the =
structure of a class has many implications, and to expect to be able to =
add/delete members at will without putting a little thought into it is =
just plain lazy.

>but my program is getting quite big=20
>(at least for me; I'm only a hobbist) and there is quite a lot to=20
>keep track of. (Actually its not quite that simple. My preferences=20
>class contains about 1/2 dozen members also derived from CObject: eg=20

If this is too much trouble for you, switch to another language.  If you =
don't
want to think, you should definitely use anything but C++.

>>(what a great source for bugs).
>>=20
>> That's absurd! Why are so many developers looking to blame their
>> bugs on somoene else?
>That's a bit unfair :(=20

No, I think he went quite easy on you. =20

>The program isn't behaving as it is supposed to=20

Only beause you programmed it wrong.  The language was working EXACTLY =
as it is designed.  Again, if you don't like it...

>.: it _is_ a bug, in my case, reasonably obvious and easy to fix.=20

>I wasn't really trying to blame anybody for anything; I just wanted to=20
>fix my problem.

It took several orders of magnitude longer to write this tome of an =
E-mail than it should have for you to fix your problem using standard =
C++ design.

> >I tried using memcpy but this didn't work (caused all sorts
> > of runtime errors).=20
>=20
> You probably have some members of your derived class that, in turn,
> own pointers.  You can't simply memcpy() stuff and expect it to work.
> Would you try to code this?

> By trying to use memcpy() yourself, you're breaking all of the
> semantics of the "has-a" objects inside of your CObject-derived
> class.
>I disagree. Using memcpy is bad because it damages the hidden class=20
>variables, but _if_ it didn't I think using memcpy (or something=20
>comparable) inside a member function (in this case operator=3D(...)
>should be ok,

So what you're saying is that although the language did exactly what it =
was supposed to, there's a bug because you wish it behaved differently.

You should _not_ be using memcpy() if you don't understand its semantics =
in the world of C++!

>No, but the folling will work, since the compilier will create a=20
>assignment operator for CElephant (Note it isn't derived from=20
>CObject):
>class CElephant
>{ public:
>    CString m_strFirstName, m_strLastName;
>};

This will NOT work!  CString maintains a reference count on its buffer,
and the default assignment will copy the reference count _without_ =
updating it!
This is perfect example of where the default assignent operator fails.

Some advice:  it is best to give examples that support your argument.

>Ok, here is where I will probably get myself in trouble again as I=20
>offer my solution.

You cannot have a solution without a problem.  There is _no_ problem =
with MFC here.  It is definitely a user issue!

I cannot repeat this enough:  if you are having trouble with this most =
basic of C++ issues, you really, really need to use some other language.

Good luck,

--Brad
--
Brad Pohl                          Custom software development & =
solutions
brad.pohl@pobox.com               Internet and intranet consulting =
services
The Brads' Consulting          Windows NT/Windows 95 software =
development
http://www.thebrads.com    Web site planning, development and =
maintenance



Mike Blaszczak -- mikeblas@nwlink.com
Sunday, January 05, 1997

At 10:57 1/6/97 +1200, Paul Martinsen wrote:

>Firstly, I believe the question is related to mfc since I'm talking 
>in particular about the way CObject is implemented. I'm sure there 
>are very good reasons for the particular implementation, but I also 
>think that I have a valid point.

You have a valid question, but you don't have a valid point.  CObject
isn't buggy because it hides operator=() to force you to implement
your own.  It's fine to ask _why_ it is set up that way, but it's
wrong to call the implementation a bug.  It would have been
irresponsible for us to implement CObject without a hidden operator=
because it doesn't affect efficiecy of applications and saves on 
bugs for the vast majority of cases.

>I want to use the default assignment operator (the one that is
>generated automatically by the compilier) to do a member by member
>copy.

CObject has an operator=() to force you to think about what you're
doing. For the all but the most trivial of useful classes that people
implement and derive (eventually) from CObject, a shallow bitwise
copy is completely wrong.

>While I was reading your replies, I realized that a default
>assignment operator won't be created for classes derived from
>CObject since they already has an assignment operator (don't suppose
>there is anyway around this?)

Sure: derive your own class from CObject and implement an operator=()
that memcpy's the members.  Then, derive everything else from your
insulation class.  It seems like it would be much easier to simply 
implement a proper operator=(), though.

>That's a bit unfair :(

It sure is.  This feature of MFC has certainly saved people from
implementing bugs more than it has caused people to code bugs.

>The program isn't behaving as it is supposed to.

That's because you made a mistake.

If I crash my motorcycle, I don't blame my motorcycle.  I try to
think of what I did wrong and how I might become a better rider.
Sometimes, that's hard to do when I'm standing around with a sprained
writst and a ripped jacket and a scraped helmet. But I do it anyway:
it's the only way I'll ever learn to become a better rider.

I can't blame the bike, or the road. Even though I might even _legally_
blame the little old lady in the green Dodge Dart, I should be able
to look back at what happened and decide how I could have braked
better or swereve better or better anticipated the situation.

I may write to my friends and explain that I was coming down
Novelty Hill Road and landed my big bum in a ditch, and ask them
what they think I might've done wrong.  But I'd certainly not write
to my friends and say that Hondas are buggy because they let me 
possibly crash.  (I wouldn't even write to them and say that Honda
should make riding motorcycles easier.)

>I disagree. Using memcpy is bad because it damages the hidden class 
>variables, but _if_ it didn't I think using memcpy (or something 
>comparable) inside a member function (in this case operator=(...)
>should be ok,

But it does.  The presumption that memcpy() doesn't break member
objects is false but for only rare exceptions. So it almost all
situations, it isn't safe to use the default assignment operator.

>No, but the folling will work, since the compilier will create a 
>assignment operator for CElephant (Note it isn't derived from 
>CObject):

No, it won't work. It'll break just as badly as the example I gave.
The compiler will end up trying to do a bitwise copy of the CString
members, and that means that the refrence counting and memory
allocation for those CString objects will be wrong.  Destroying
the object will likely cause memory leaks and will probably
cause other CStrings in your app to get sick, for example.

Please prove it to yourself: _try_ to use that code; you'll quickly
see that it breaks in even the most trivial situations.

>I could derive 
>from CObject, use these routines and not have to worry about 
>assignment operators.

Unfortunately, you do need to worry about assignment operators.
CObject is coded the way it is so that you're forced to worry about
assignment operators.


.B ekiM
http://www.nwlink.com/~mikeblas/
Why does the "new" Corvette look like a 1993 RX-7?
These words are my own. I do not speak on behalf of Microsoft.




Paul Martinsen -- pmartinsen@hort.cri.nz
Tuesday, January 07, 1997

Environment: VC4.0, Win 95.

Look I am seriously trying to discuss an issue here -- not blaim any
body for anything. I appreciate I am not an expert on programming,
C++ or MFC. 

I think this list may getting tired of the topic, so I shall try to
make this my last post on the subject, but I would like to clear up a
few points if the moderator will permit me. 
If you feel a reply is warranted, maybe we should resort to private 
email. (Reach me at martinsenp@hort.cri.nz). 

1. I did _not_ say that CObject, or MFC is buggy. I wrote that
having to write an assignment operator to do a member by member copy
for a class _may_ allow a bug to creep in when the original class is
modified. For example, suppose another variable is added to the class 
but I forget to add it to the assignment operator. This would be a 
bug introduced by ME not by MFC, even if I was using CObject as a 
base class.

2. Suppose I have the following class.
class CFoo: public CObject
{ public:
    int a, b, c;
    CFoo operator=3D(CFoo src);
}
> Sure: derive your own class from CObject and implement an
> operator=3D() that memcpy's the members.  Then, derive everything else
> from your insulation class.  It seems like it would be much easier
> to simply implement a proper operator=3D(), though.
> 
I think (I belive I am agreeing with you) that:
CFoo CFoo::operator=3D(CFoo src)
{ memcpy(&a,&src.a,sizeof(int)* 3);
}
would be a poor way to implement the copy operator. I think it is not 
very clear what is going on, and feel it would be a mine of bugs 
after any modifications to the class. Note I could not use sizeof(*this) i=
n the 
memcpy function since the size returned would include space allocated 
for variables implemented by the compilier such as the this pointer 
which it is not safe to copy. In adition there may situations where 
it would not work, such as if CFoo contained members of user defined 
classes.

3.
> CObject has an operator=3D() to force you to think about what you're
> doing. 
I appreciate this, but with the current implementation it is not 
possible to use the default C++ behaviour of a member-by-member 
assignment-- even after I have thought about it and decided that is 
what I want.

4.
> For the all but the most trivial of useful classes that people
> implement and derive (eventually) from CObject, a shallow bitwise
> copy is completely wrong.
But it is what the language defines. I think a better class structure 
would have been:
CObject -- without assignment operator
  CWindowsObject -- with protected assignment operator
    CWnd etc. -- have to create special assignment operator
                 top copy CWnd object since not very sensible
                 thing to do.
  CPerson -- Application defined class has no assignment operator
             so compilier generates default member-by-member
             assignment, which developer has thought about &
             knows is ok. Still can by used in Serialize etc.
This way the developer can create Data objects, where in many cases 
(at least in my, probably limited, experience) a member by member 
copy is meanginful. As I said last time, there may be very good 
reasons why this is not practical.

5
> No, it won't work. It'll break just as badly as the example I gave.
> The compiler will end up trying to do a bitwise copy of the CString
> members, and that means that the refrence counting and memory
> allocation for those CString objects will be wrong.  Destroying the
> object will likely cause memory leaks and will probably cause other
> CStrings in your app to get sick, for example.
> 
> Please prove it to yourself: _try_ to use that code; you'll quickly
> see that it breaks in even the most trivial situations.
This is not true, or we are talking at cross-purposes.
First from the online C++ documentation on "Memberwise Assignment and 
Initialization"
The methods for default assignment and initialization are =93memberwise
assignment=94 and =93memberwise initialization,=94 respectively. Memberwis=
e
assignment consists of copying one object to the other, a member at a
time, as if assigning each member individually. Memberwise....
 
Default assignment operators for memberwise assignment cannot be
generated if any of the following conditions exist: 
=B7	A member class has const members. 
=B7	A member class has reference members. 
=B7	A member class or its base class has a private assignment operator (op=
erator=3D).
            ^^ this is what is causing the trouble
=B7	A base class or member class has no assignment operator (operator=3D).
Thus, if I have interpreted correctly, given the following class, and 
definitions:
class CElephant
{ public:
    CString str1, str2;
};

  CElephant e1,e2;

  e1.str1 =3D "Hello";
  e1.str2 =3D "there";

These two code segments achieve exactly the same result:
/*1. */  e2 =3D e1;

/*2.*/  e2.str1 =3D e1.str1;
        e2.str2 =3D e1.str2
Steping through the code, I discoved that the CString function
const CString& CString::operator=3D(const CString& stringSrc) gets 
called and executes the code indicated by <---
const CString& CString::operator=3D(const CString& stringSrc)
{
 if (m_pchData !=3D stringSrc.m_pchData)
 {
  if ((GetData()->nRefs < 0 && GetData() !=3D afxDataNil) ||
   stringSrc.GetData()->nRefs < 0)
  {
   // actual copy necessary since one of the strings is locked
   AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);
  }
  else
  {
   // can just copy references around   <--- Executes this.
   Release();
   ASSERT(stringSrc.GetData() !=3D afxDataNil);
   m_pchData =3D stringSrc.m_pchData;
   InterlockedIncrement(&GetData()->nRefs);
  }
 }
 return *this;
}
After copying e1 to e2, I can execute
e1.str1 =3D "Something else";
I then inspected e1 & e2, and found there members to be:
e1.str1 =3D "Something else";
e1.str2 =3D "there"
e2.str1 =3D "Hello"
e2.str2 =3D "there"
This is what I expected.

In summary my understanding is that the default assignment operator
does not do a memcpy copy, but a member-by-member assigment. This 
seems to disagree with what Mike says. Have I missed something Mike?
Mike also said:
> So it almost all
> situations, it isn't safe to use the default assignment operator.
I disagree, I would argue that it is safe to use the assignment 
operator unless your class contains pointers to objects that it owns. 
For example a default assignment in the following case is not safe:
class CFoo
{ public
    CFoo()
    { m_pMyData =3D new char[100];
    }
    ~CFoo()
    { delete m_pMyData;
    }
    char *m_pMyData;
};
A member-by-member copy will overwrite the m_pMyData variable so the 
wrong data will be deleted when the destructor is called.
Finally, if people are writing code like the above, MFC has done them 
a great service by blocking the possibility for objects derived from 
CObject. But for the rest of us, it is a real pain. I guess the 
upshot is that this is a style decision which different programmers 
will choose differently and maybe there is no right or wrong.
Kind Regards,
Paul Martinsen.




Ken Nicolson -- kenn@owl.co.uk
Tuesday, January 07, 1997

[I'm going to reference the C++ FAQ Book by Cline & Lomov, this holiday's
reading material - an excellent book! - so when you see FAQ #123, it's a
reference to one of the questions in the book]

On Tue, 7 Jan 1997 11:43:41 +1200, Paul Martinsen wrote:

>Environment: VC4.0, Win 95.

[snip!]

>I think this list may getting tired of the topic, so I shall try to
>make this my last post on the subject, but I would like to clear up a
>few points if the moderator will permit me.=20
>If you feel a reply is warranted, maybe we should resort to private=20
>email. (Reach me at martinsenp@hort.cri.nz).=20

I've emailed you with more details, but I thought I'd try to clear a few
things up for the list.

>1. I did _not_ say that CObject, or MFC is buggy. I wrote that
>having to write an assignment operator to do a member by member copy
>for a class _may_ allow a bug to creep in when the original class is
>modified. For example, suppose another variable is added to the class=20
>but I forget to add it to the assignment operator. This would be a=20
>bug introduced by ME not by MFC, even if I was using CObject as a=20
>base class.

That's a problem with C++ compilers, and probably where one needs a C++
Lint. Once a copy ctor and =3Dop is introduced (even to classes outside =
the
CObject hierarchy with, say, memory allocation), it would be nice to get =
a
warning to say that a member is not copied.

>2. Suppose I have the following class.
>class CFoo: public CObject
>{ public:
>    int a, b, c;
>    CFoo operator=3D(CFoo src);
>}
>> Sure: derive your own class from CObject and implement an
>> operator=3D() that memcpy's the members.  Then, derive everything else
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Yuk - who said that? Memcpy is totally wrong for objects! FAQ #208: "Why
does a program crash when you memcpy() an object? A: Because bitwise
copying is evil."

>> from your insulation class.  It seems like it would be much easier
>> to simply implement a proper operator=3D(), though.
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

OK, that's better.

[snip poor assignment operator]

>3.
>> CObject has an operator=3D() to force you to think about what you're
>> doing.=20
>I appreciate this, but with the current implementation it is not=20
>possible to use the default C++ behaviour of a member-by-member=20
>assignment-- even after I have thought about it and decided that is=20
>what I want.

Thinking about it, maybe CObject could be an abstract class with pure
virtual functions, so then a private copy constructors make sense, but
since I believe the object needs to be instantiated to do things like
persistence, it cannot be pure virtual.

Again, the FAQ book has a whole chapter, 17, on User-Defined Assignment
Operators - FAQs #211 - #220.

>4.
>> For the all but the most trivial of useful classes that people
>> implement and derive (eventually) from CObject, a shallow bitwise
>> copy is completely wrong.
>But it is what the language defines. I think a better class structure=20
>would have been:
>CObject -- without assignment operator
>  CWindowsObject -- with protected assignment operator
>    CWnd etc. -- have to create special assignment operator
>                 top copy CWnd object since not very sensible
>                 thing to do.
>  CPerson -- Application defined class has no assignment operator
>             so compilier generates default member-by-member
>             assignment, which developer has thought about &
>             knows is ok. Still can by used in Serialize etc.
>This way the developer can create Data objects, where in many cases=20
>(at least in my, probably limited, experience) a member by member=20
>copy is meanginful. As I said last time, there may be very good=20
>reasons why this is not practical.

Maybe the design issue is that serialisation could be implemented in a
separate object, as it's a nice feature, but because it appears in the
CObject hierarchy, it's stuck in a predefined universe.

>5
>> No, it won't work. It'll break just as badly as the example I gave.

I can't find this example - was it private email?

[snip discussion of compiler-generated assignment operators]

>Thus, if I have interpreted correctly, given the following class, and=20
>definitions:
>class CElephant
>{ public:
>    CString str1, str2;
>};
>
>  CElephant e1,e2;
>
>  e1.str1 =3D "Hello";
>  e1.str2 =3D "there";
>
>These two code segments achieve exactly the same result:
>/*1. */  e2 =3D e1;
>
>/*2.*/  e2.str1 =3D e1.str1;
>        e2.str2 =3D e1.str2

Correct. FAQ #211. Surely is isn't being said that the compiler would
produce:

/*3.*/  memcpy( &e2.str1, &e1.str1, sizeof( e1.str1 ) );
        memcpy( &e2.str2, &e1.str2, sizeof( e1.str2 ) );

(ignoring sizeof() issues of course)?

>Steping through the code, I discoved that the CString function

It's one of the best ways of proving to yourself what happens!

[snip CString code example]

>In summary my understanding is that the default assignment operator
>does not do a memcpy copy, but a member-by-member assigment. This=20
>seems to disagree with what Mike says. Have I missed something Mike?
>Mike also said:
>> So it almost all
>> situations, it isn't safe to use the default assignment operator.
>I disagree, I would argue that it is safe to use the assignment=20
>operator unless your class contains pointers to objects that it owns.=20

Agreed. In the book, there is chapter 16 on The Big Three, dtor, copy =
ctor
and =3Dop, that describes what happens when. If you need one, you need =
all
three.

>For example a default assignment in the following case is not safe:
>class CFoo

     : public CObject
I assume?

>{ public
>    CFoo()
>    { m_pMyData =3D new char[100];
>    }
>    ~CFoo()
>    { delete m_pMyData;
>    }
>    char *m_pMyData;
>};
>A member-by-member copy will overwrite the m_pMyData variable so the=20
>wrong data will be deleted when the destructor is called.
>Finally, if people are writing code like the above, MFC has done them=20
>a great service by blocking the possibility for objects derived from=20
>CObject.

The Law of the Big Three says: have one, have them all. This object
violates this, and CObject helps to remind you. This is A Good Thing.
> But for the rest of us, it is a real pain. I guess the=20
>upshot is that this is a style decision which different programmers=20
>will choose differently and maybe there is no right or wrong.

I think the only conclusion I can draw is that you have to always write
your own Big Three for your CObject-derived classes.

Or, another idea has sprung to mind. Add your own intermediate
CMyCopyableObject: (tested for syntax, not for behaviour...)

class CMyCopyableObject : public CObject
{
public:
	CMyCopyableObject() {}
	CMyCopyableObject(const CMyCopyableObject& objectSrc) {}
	void operator=3D(const CMyCopyableObject& objectSrc) {}
};

Now you can make your own decisions as to whether or not you need to
implement The Big Three in sub-classes.

[Bizzarly, my spell-checker wants to correct CMyCopyableObject to
CYtOmegalovIrus!]

>Kind Regards,
>Paul Martinsen.

Ken



Tim Robinson -- timtroyr@ionet.net
Tuesday, January 07, 1997

At 11:43 1/7/97 +1200, "Paul Martinsen"  wrote:
>Environment: VC4.0, Win 95.
>
>Look I am seriously trying to discuss an issue here -- not blaim any
>body for anything. I appreciate I am not an expert on programming,
>C++ or MFC. 

Paul, you ask some good questions, but I think you are missing something in
your knowledge of C++ and class libraries.  This is not an insult.  If we're
not "missing something" we're not growing as programmers.  I have my share
of things I still need to know.  I also have what I feel are legitimate
gripes about the way MFC was designed and the flaws I see come from someone
early on thinking too much like a C programmer and not enough like a C++
programmer.  But the latter is not my point at the moment.  Let's continue
through your questions and I'll try to keep this as relevent to MFC as possible.

[points 1-2 clipped.  They went over the difficulties of creating an operator=]

We're talking about difficulty through complexity here.  In a simple class
containing simple data types and no 1) pointers, 2) handles or 3) virtual
functions, the classic member copy is safe.  By no means can CObject be
classified as 'simple.'  The MFC designers were in an odd position of making
this safe for the users.  (Yes, *we* are users when we use a class they
designed.)  The simple member copy is simply wrong.  But CObject can't be
psychic about what is derived from it.  Consequently, the only safe move by
the designers was to effectively block the copy ctor and the operator=.  It
is a move I've encouraged for my students when calls by value or assignments
are improper for a class.  But it's not the only reason for this.  CObject
demonsrates another reason.

[aside: Before you ask why I included virtual functions in the design of a
non-simple class, I'll tell you.  A while back when poking through the ANSI
standard for C++ I noted that actual implementation of virtual functions
indicated that method was left to the designer of the compiler.  This little
issue yanks a class from the realm of being a data-only structure to being a
true C++ style complex class.]

>3.
>> CObject has an operator=() to force you to think about what you're
>> doing. 

While the operator=() does this and it's a "good thing," it is not the whole
truth as I've noted above.

>I appreciate this, but with the current implementation it is not 
>possible to use the default C++ behaviour of a member-by-member 
>assignment-- even after I have thought about it and decided that is 
>what I want.

Also, as I noted above, it can't be what you want.

>4. [snipped]
>5  [most snipped]
>Steping through the code, I discoved that the CString function
>const CString& CString::operator=(const CString& stringSrc) gets 
>called and executes the code indicated by <---
>const CString& CString::operator=(const CString& stringSrc)
>{
> if (m_pchData != stringSrc.m_pchData)
> {
>  if ((GetData()->nRefs < 0 && GetData() != afxDataNil) ||
>   stringSrc.GetData()->nRefs < 0)
>  {
>   // actual copy necessary since one of the strings is locked
>   AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);
>  }
>  else
>  {
>   // can just copy references around   <--- Executes this.
>   Release();
>   ASSERT(stringSrc.GetData() != afxDataNil);
>   m_pchData = stringSrc.m_pchData;
>   InterlockedIncrement(&GetData()->nRefs); <--- Tim's annotation
>  }
> }
> return *this;
>}
>In summary my understanding is that the default assignment operator
>does not do a memcpy copy, but a member-by-member assigment. This 
>seems to disagree with what Mike says. Have I missed something Mike?

I'm not Mike, but I'll tell you you missed something.  My annotation above
indicates a point where a locking count is incremented.  This is similar to
the locking count associated with doing GlobalLock().  In the usage you
cited, two CString objects wound up having pointers to the same memory
location.  With shallow copying and no locking, deleting one CString and
associated data pointed to, would leave the second pointing to bad data.
But with the locking mechanism in place, deleting one CString would merely
decrement the lock.  Only on destruction of the last CString to use the data
object does the data get freed.  Stroustrup does a fair write-up on this
subject in the gray book (and you should have that book if you're a C++
programmer).

>> So it almost all
>> situations, it isn't safe to use the default assignment operator.
>I disagree, I would argue that it is safe to use the assignment 
>operator unless your class contains pointers to objects that it owns. 

Being redundant: not just pointers, but also handles and virtual functions.

>Finally, if people are writing code like the above, MFC has done them 
>a great service by blocking the possibility for objects derived from 
>CObject. But for the rest of us, it is a real pain. I guess the 
>upshot is that this is a style decision which different programmers 
>will choose differently and maybe there is no right or wrong.

Unfortunately, this isn't as gray as I think you are assuming.  MFC didn't
just do a "nice thing," it did the "right thing."  But this doesn't get you
closer to a solution for your problems.  So let me make some recommendations.

1) A sufficiently complex class probably should not allow assignment or
passing by value.  Stick your head inside ostream.h to see an example of
something more "official" than MFC.

2) If you've decided your class should still allow copying (like CString)
and the volume of data is sufficient enough to cause programmer error should
there be a change, perhaps you should look into a helper function of some
kind.  Something that makes assignment or copying safer and more consistent.

Finally, there is nothing that can save us from ourselves.  Programmers will
never stop making mistakes.  Especially mistakes of the type of adding a
member var and failing to add it to an initializer list or to a deep copy.
Given your analysis of the CString::operator=() and your analysis of your
current C++ programming state, I'll also offer personal advice: spend
another year with C++, write a class lib or two yourself, and teach some C++
to somone else... then re-ask your question about CObject.  Good luck.

| Tim Robinson, Esquire          | Liberty  means responsibility. |
| timtroyr@ionet.net             | That is why most men dread it. |
| http://www.ionet.net/~timtroyr |            George Bernard Shaw |




Martin J. Mahoney -- mjm@PyramidLogicSystems.com
Wednesday, January 08, 1997

> From: Mike Blaszczak 
> To: mfc-l@netcom.com; mfc-l@netcom.com
> Subject: Re: Please let me make a copy
> Date: Monday, January 06, 1997 1:36 AM
> 
> At 10:57 1/6/97 +1200, Paul Martinsen wrote:
> 
  (Code Edited out)
 
> >I want to use the default assignment operator (the one that is
> >generated automatically by the compilier) to do a member by member
> >copy.
> 
> CObject has an operator=() to force you to think about what you're
> doing. For the all but the most trivial of useful classes that people
> implement and derive (eventually) from CObject, a shallow bitwise
> copy is completely wrong.
> 

The 1.0 standard of C++ did a shallow bitwise copy for the default copy 
and assignment operators but by the release of the 2.0 Standard it had c
hanged to Member wise.

In Bjarne Stroustrup's The C++ Programming Language (Second Edition)
Copyright 1991, Reprinted with correction  December 1994
Addison Wesley
ISBN 0-201-53992-6

On Page 582 he talks about assignment and initialization of a class 
and states:
  "Conceptually, for a class X these two operations are implemented 
   by an assignment operator and a copy constructor ($r.12.1). The 
   programmer may define one or both of these. If not defined by the
   programmer, they will be defined as memberwise assignment and 
   memberwise initialization of the members of X, respectively."

About 4 paragraphs later he states:
  "Memberwise assignment and memberwise initialization implies that
   if a class X has a member of class M, M's assignment operator and 
   M's copy constructor are used to implement assignment and
   initialization of the member, respectively."

What I think people need to realize, however,  is that MFC has been around
a long time and when the decision to privatize the assignment operator was 
made, the default assignment operator did a bitwise copy and therefore was 
not safe.

(Rest of thread Edited out)

Martin J. Mahoney
Pyramid Logic Systems
http://www.PyramidLogicSystems.com





| Вернуться в корень Архива |