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 BlaszczakAt 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
| Вернуться в корень Архива |