- Why does it not have a try for this throw? - 3 Updates
- Question about derived class member function call - 4 Updates
- Avoid 'int' and associates. - 5 Updates
- How to call virtual functions in C++ - 7 Updates
- How to call virtual functions in C++ - 6 Updates
| fl <rxjwg98@gmail.com>: Jun 15 07:04PM -0700 Hi, I am new to throw, and C++. When I see the following snippet, I go to learn throw. On the throw topic, it also gives try and catch. The following snippet does not looks like written by a beginner. Could you explain it to me on this throw usage? Thanks, ....................... class Number { friend class RealNumber; friend class Complex; public: Number (); Number & operator = (const Number &n); Number (const Number &n); virtual ~Number(); virtual Number operator + (Number const &n) const; void swap (Number &n) throw (); static Number makeReal (double r); static Number makeComplex (double rpart, double ipart); protected: Number (BaseConstructor); private: void redefine (Number *n); virtual Number complexAdd (Number const &n) const; virtual Number realAdd (Number const &n) const; Number *rep; short referenceCount; }; |
| Melzzzzz <mel@zzzzz.com>: Jun 16 05:11AM +0200 On Mon, 15 Jun 2015 19:04:08 -0700 (PDT) > learn throw. On the throw topic, it also gives try and catch. The > following snippet does not looks like written by a beginner. Could > you explain it to me on this throw usage? That means empty exception specification. If function throws terminate will be called, if not unexpected_handler installed. |
| legalize+jeeves@mail.xmission.com (Richard): Jun 16 02:25PM [Please do not mail me a copy of your followup] fl <rxjwg98@gmail.com> spake the secret code > Could you explain it to me on this throw usage? Seriously, what book are you guys getting all this really confusing code from? > void swap (Number &n) throw (); This is an exception specification, which you can read about here: <http://en.cppreference.com/w/cpp/language/except_spec> This is deprecated since C++11 -- in other words for almost 5 years, it has been recommended that you NOT USE THIS. Why is this the recommendation? Because exception specifiers are an idea borrowed from Java and they ended up causing more problems in C++ than they solved because C++ is not Java ;-).[*] The intention with the 'throw()' exception specification is to say that this function does not throw. This is the one useful thing that exception specifications gave C++ and the reason why C++11 introduced the noexcept keyword, which should be used instead: <http://en.cppreference.com/w/cpp/language/noexcept_spec> [*] This is my opinion. But seriously, this is usenet; do I really have to qualify anything in my posts as my opinion? Whose opinion would be if it wasn't mine? -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| fl <rxjwg98@gmail.com>: Jun 15 07:59PM -0700 Hi, When I run the following code, I am puzzled about ............... #include <iostream> using namespace std; struct BaseConstructor { BaseConstructor(int=0) {} }; class RealNumber; class Complex; class Number; class Number { friend class RealNumber; friend class Complex; public: Number (); Number & operator = (const Number &n); Number (const Number &n); virtual ~Number(); virtual Number operator + (Number const &n) const; void swap (Number &n) throw (); static Number makeReal (double r); static Number makeComplex (double rpart, double ipart); protected: Number (BaseConstructor); private: void redefine (Number *n); virtual Number complexAdd (Number const &n) const; virtual Number realAdd (Number const &n) const; Number *rep; short referenceCount; }; class Complex : public Number { friend class RealNumber; friend class Number; Complex (double d, double e); Complex (const Complex &c); virtual ~Complex (); virtual Number operator + (Number const &n) const; virtual Number realAdd (Number const &n) const; virtual Number complexAdd (Number const &n) const; double rpart, ipart; }; class RealNumber : public Number { friend class Complex; friend class Number; RealNumber (double r); RealNumber (const RealNumber &r); virtual ~RealNumber (); virtual Number operator + (Number const &n) const; virtual Number realAdd (Number const &n) const; virtual Number complexAdd (Number const &n) const; double val; }; /// Used only by the letters. Number::Number (BaseConstructor): rep (0), referenceCount (1) { cout << "rep(0), referenceCount(1)\n" << rep << ", " << referenceCount << endl; } /// Used by user and static factory functions. Number::Number () : rep (0), referenceCount (0) {} /// Used by user and static factory functions. Number::Number (const Number &n): rep (n.rep), referenceCount (0) { cout << "Constructing a Number using Number::Number\n"; if (n.rep) n.rep->referenceCount++; } Number Number::makeReal (double r) { Number n; n.redefine (new RealNumber (r)); return n; } Number Number::makeComplex (double rpart, double ipart) { Number n; n.redefine (new Complex (rpart, ipart)); return n; // nCaller } int main (void) { Number::makeComplex (1, 2); cout << "Finished\n"; return 0; } |
| fl <rxjwg98@gmail.com>: Jun 15 08:12PM -0700 Hi, Excuse me for accidentally post the incomplete message previously. In main(), line Number::makeComplex (1, 2); calls Number Number::makeComplex (double rpart, double ipart) Then, it calls constructor: Number::Number () : rep (0), referenceCount (0) Next, it calls Complex::Complex (double d, double e) : Number (BaseConstructor()), rpart (d), ipart (e) { cout << "Constructing a Complex\n"; } Next, it calls Complex constructor: Complex::Complex (double d, double e) : Number (BaseConstructor()), rpart (d), ipart (e) Next, it calls member function: void Number::redefine (Number *n) My question comes here. When it runs line return n; // nCaller It calls constructor: Number::Number (const Number &n): rep (n.rep), referenceCount (0) I don't understand why it calls the above constructor. For your reference, here is the console window message: rep(0), referenceCount(1) 00000000, 1 Constructing a Complex Constructing a Number using Number::Number I think that I have not enought understanding on class inheritance. Hopefully, the answer for this question can teach me some. Can you help me out? Thanks, ............... #include <iostream> using namespace std; struct BaseConstructor { BaseConstructor(int=0) {} }; class RealNumber; class Complex; class Number; class Number { friend class RealNumber; friend class Complex; public: Number (); Number & operator = (const Number &n); Number (const Number &n); virtual ~Number(); virtual Number operator + (Number const &n) const; void swap (Number &n) throw (); static Number makeReal (double r); static Number makeComplex (double rpart, double ipart); protected: Number (BaseConstructor); private: void redefine (Number *n); virtual Number complexAdd (Number const &n) const; virtual Number realAdd (Number const &n) const; Number *rep; short referenceCount; }; class Complex : public Number { friend class RealNumber; friend class Number; Complex (double d, double e); Complex (const Complex &c); virtual ~Complex (); virtual Number operator + (Number const &n) const; virtual Number realAdd (Number const &n) const; virtual Number complexAdd (Number const &n) const; double rpart, ipart; }; class RealNumber : public Number { friend class Complex; friend class Number; RealNumber (double r); RealNumber (const RealNumber &r); virtual ~RealNumber (); virtual Number operator + (Number const &n) const; virtual Number realAdd (Number const &n) const; virtual Number complexAdd (Number const &n) const; double val; }; /// Used only by the letters. Number::Number (BaseConstructor): rep (0), referenceCount (1) { cout << "rep(0), referenceCount(1)\n" << rep << ", " << referenceCount << endl; } /// Used by user and static factory functions. Number::Number () : rep (0), referenceCount (0) {} /// Used by user and static factory functions. Number::Number (const Number &n): rep (n.rep), referenceCount (0) { cout << "Constructing a Number using Number::Number\n"; if (n.rep) n.rep->referenceCount++; } Number Number::makeReal (double r) { Number n; n.redefine (new RealNumber (r)); return n; } Number Number::makeComplex (double rpart, double ipart) { Number n; n.redefine (new Complex (rpart, ipart)); return n; // nCaller } int main (void) { Number::makeComplex (1, 2); cout << "Finished\n"; return 0; } |
| Melzzzzz <mel@zzzzz.com>: Jun 16 05:14AM +0200 On Mon, 15 Jun 2015 19:59:16 -0700 (PDT) > BaseConstructor(int=0) > {} > }; I am puzzled buy this, too... |
| legalize+jeeves@mail.xmission.com (Richard): Jun 16 02:18PM [Please do not mail me a copy of your followup] fl <rxjwg98@gmail.com> spake the secret code > BaseConstructor(int=0) > {} >}; Didn't we already go over this in another thread? It feels like a bunch of people are taking a summer course in C++ using a confusing and poorly written book and the instructor has recommended you come here for help. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
| James Kuyper <jameskuyper@verizon.net>: Jun 15 09:04PM -0600 On 06/15/2015 05:54 PM, Mr Flibble wrote: > On 15/06/2015 21:54, Francis Glassborow wrote: ... >> extended types, and where they have, they are often not portable (there >> is no guarantee that any of the exact types exist on a platform) > Can you back that assertion up with any facts at all? Which assertion are you referring to? Normally, "that" refers to the most recently mentioned thing that it could refer to, but his very last assertion is easy to back up: 7.20.1.1, describing the Exact-width Types, says in paragraph 3: "These types are optional". His earlier assertions are much more questionable, but if you meant to refer to one of them, you should have used some phrase other than "that assertion". ... > concerned the MISRA C++ coding standard for safey critical systems > prohibits the use of basic numerical types and mandates the use of the > typedefs from <cstdint> (or an equivalent). I'm not impressed by MISRA's guidelines in general, and this one fits into that pattern. I've only heard of them second-hand; a copy of those guidelines is too expensive to justify buying one if you don't have an obligation to follow those guidelines. However, what I've heard from other people about those guidelines has not impressed me (and most of those people were MISRA supporters). -- James Kuyper [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| James Kuyper <jameskuyper@verizon.net>: Jun 15 09:04PM -0600 On 06/15/2015 04:54 PM, Francis Glassborow wrote: ... > over and above those required by the standard to allow programmers to > express their needs. And the rationale was a purely C one and I doubt > that it would be in C++ other than for compatibility reasons. I don't see that. What features of the rationale for the size-named types are C specific? I see the desire to minimize either memory use or processing time (and to have some control over which one is minimized) as being pretty much universal, and especially so in lower-level languages like C and C++. > For example it is possible that on a particular platform a 32-bit > integer type performs faster than a 16-bit one. However that has a smell > of premature optimisation. I don't see how it could be. If that's premature, when is "maturity" to be achieved? That is, when should the decision be made to choose a type based upon it's speed rather than it's size (or vice versa)? > In reality very few if any programmers have ever made use of those > extended types, and where they have, they are often not portable (there > is no guarantee that any of the exact types exist on a platform) I am mainly interested in the least- and fast-sized types, which are guaranteed, so that's not a problem for me. And, in the rare circumstance where I'd want to use an exact-sized type, the fact that I decided to use it would imply, correctly, that the program would be completely irrelevant to any platform which didn't support that type: for instance, to implement the interface to hardware that, if attached to a given system, necessarily implies that the exact-sized type must be supported. Note: I expect many people to use exact-sized types inappropriately, under circumstances were that wouldn't be true - but it's not something I would do. -- James Kuyper [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| David Brown <david.brown@hesbynett.no>: Jun 16 10:10AM +0200 On 15/06/15 18:26, Mr Flibble wrote: > quote the MISRA C++ coding standard: > "typedefs that indicate size and signedness should be used in place of > the basic numerical types." I agree with the fundamental principle that "general" C and C++ programming could benefit from following some of the same sort of coding standards and guidelines as are used in embedded development. However... There is a big difference between the quality and safety required for different domains. It is acceptable that that a video game has glitches, and even the risk of the occasional crash - it's a different matter for a aircraft flight control system or an artificial respirator. On the other hand, top-of-the-range quality controlled software development has code productivity rates of a few lines of code per week per developer - that is not going to be suitable for mainstream development. There are also big differences in the sorts of coding rules that suit small-system embedded development, and big system programming. In the types of systems MISRA is targeted at, dynamic memory allocation is often banned (for very good reasons) - imagine trying to program "minesweeper" for Windows without "new", "malloc", or any OS calls that used dynamic memory. And as coding standards go, I've never been overly impressed with MISRA. A lot of the rules are obvious and even superfluous, such as spelling out (and banning) different sorts of ways to write code that is hard to read, or banning undefined behaviour, or banning overlapping and mixing try/catch blocks with switches and gotos. And some rules are downright /wrong/, such as insisting on writing "if (1 == x)" as a way of protecting against "if (x = 1)" mistakes - instead, you should get a decent compiler or linter, and learn how to use the warnings. There are many rules from embedded coding standards that would be good in "big system" programming - but it would be even better to develop and follow appropriate rules for the domain you are working in. And as for this specific case, there is a lot more need of size-specific types in embedded systems than in general C or C++ programming - while I think many general C and C++ programmers should use them more, using them everywhere is unnecessary even in embedded programming. |
| David Brown <david.brown@hesbynett.no>: Jun 16 07:04AM -0600 On 15/06/15 19:22, Francis Glassborow wrote: > least 32-bits, so why would I not use that when I need a 32-bit integer? > That is why int32_t better? Note that on systems with 32-bit int, long > int is usually also 32 bits. There are a few reasons to prefer int32_t to long. One is that when you need 32 bits, it is no bigger than necessary - long is 64-bit on many of the most common systems these days (such as most 64-bit *nix systems - in particular, 64-bit Linux and BSD on x86-64). Another is that it says exactly what you mean - it is absolutely clear in the code that you picked that type because you wanted 32 bits. You didn't just use a "long" because you thought the data might be a bit big, or because you guessed a particular size for "long", or because your code has ancestry back to 16-bit Windows and used "long" to hold pointers. > And where I need 64 bits the (ugly) long long int meets my need. Yes, but "long long int" is /ugly/ ! And again, int64_t says exactly what you mean. -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| David Brown <david.brown@hesbynett.no>: Jun 16 07:05AM -0600 On 16/06/15 05:04, James Kuyper wrote: > obligation to follow those guidelines. However, what I've heard from > other people about those guidelines has not impressed me (and most of > those people were MISRA supporters). MISRA standards are only £10 or £15 for the pdfs - it's not /that/ expensive! (But of course any cost makes the whole process a lot more inconvenient, especially if you just want a quick look for curiosity.) -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |
| Victor Bazarov <v.bazarov@comcast.invalid>: Jun 15 08:47PM -0400 On 6/15/2015 7:16 PM, Stefan Ram wrote: > ::std::cout << sizeof( IntListEntry )<< '\n'; > ::std::cout << sizeof( CharListEntry )<< '\n'; > and always got the same value »16«. That doesn't matter, Paavo was not being literal, he was trying to lighten your mood while telling you that you did something dumb. When you try to "squeeze" a derived object into a container of base class objects, every time you "push_back" or "insert", behind the scenes the compiler creates a copy. But just before it starts chiseling that copy, it needs to convert the original into the type that your container is declared to store. The compiler takes the *object* of the derived type and extracts from it the base class object with all base class *attributes*, including the *virtual function mechanism* (whatever it might be). It's known as "slicing" because the compiler removes _as if by a knife_ whatever makes that original object the derived type, and you're left with the core, the pit, the bone that represents the complete and fully functional (in all senses) base class object. Calling any virtual function leads to only executing the code in the base class because the container only contains the objects of the base class. All the specifics of the derived class object have been sliced off, removed, *lost*. > [..] V -- I do not respond to top-posted replies, please don't ask |
| Martin Shobe <martin.shobe@yahoo.com>: Jun 15 08:00PM -0500 On 6/15/2015 7:47 PM, Victor Bazarov wrote: > base class because the container only contains the objects of the base > class. All the specifics of the derived class object have been sliced > off, removed, *lost*. And when he's fixed that, he can add const to the base class's print so the derived classes can override it. Martin Shobe |
| Victor Bazarov <v.bazarov@comcast.invalid>: Jun 15 10:19PM -0400 On 6/15/2015 8:55 PM, Stefan Ram wrote: >> is declared to store. > That's why I eventually resorted to »memcpy« for the copy. > But even that did not help! Using memcpy to copy C++ objects is like using an axe to perform appendectomy. It might work in a very small percentage of cases under very specific conditions, but definitely not for classes with virtual functions, as you have learned. That failure should teach you to refrain from using C functions for memory manipulation, especially when you're not sure in what you're doing. And definitely not to try to circumvent some mechanisms built into the language (like slicing, some pointer-to-member conversions, etc.) >> the pit, the bone that represents the complete and fully functional (in >> all senses) base class object. > Ok, that might explain it. I really should read more about slicing! Eventually those things will happen to you enough times that you'll commit them to your long-term memory... V -- I do not respond to top-posted replies, please don't ask |
| Martin Shobe <martin.shobe@yahoo.com>: Jun 15 09:41PM -0500 On 6/15/2015 9:18 PM, Stefan Ram wrote: > That is, it has placed an instance of B and an instance of D > into my container, and I can now call the virtual p() with the > /run-time type/ B or D instead of the static container type B. You got unlucky. The behavior is undefined. Martin Shobe |
| Victor Bazarov <v.bazarov@comcast.invalid>: Jun 15 10:42PM -0400 On 6/15/2015 10:18 PM, Stefan Ram wrote: > struct D : B { void p() { ::std::cout << "D\n"; }}; > int main() > { struct B a[ 2 * sizeof( B )]; Why do you think you need to multiply '2' by the sizeof ? How many elements do you think your array needs? Also, are you a C programmer? When declaring an object (or an array of objects) of type B, you don't need to say 'struct'... > new( static_cast< void * >( a + 0 ))B(); > new( static_cast< void * >( a + 1 ))D(); This is called "placement new". You take the memory you have and build an object of some other class in that memory. I think there is no need for you to 'static_cast' them to 'void*', BTW. Try without them. > That is, it has placed an instance of B and an instance of D > into my container, and I can now call the virtual p() with the > /run-time type/ B or D instead of the static container type B. Yes, you faked it, hacked it. You constructed an object of derived type in the memory initially given to an object of the base type. Not to mention that you also constructed those 'B' objects as 'B' ones (when you defined your 'a' array) and then *replaced* one of them with a new'ed object of a different type (doesn't really matter that they are related). And how are they destroyed? They are destroyed as objects of type B, which leads to undefined behaviour AFA a[1] is concerned. Not to mention that a[0] is constructed *twice* and destructed only once (when 'a' goes out of scope). How many rules you've violated? A bunch? That's why it's a hack and not C++ programming... V -- I do not respond to top-posted replies, please don't ask |
| Victor Bazarov <v.bazarov@comcast.invalid>: Jun 15 10:47PM -0400 On 6/15/2015 10:37 PM, Stefan Ram wrote: >> appendectomy. > Ok, now I got it working here. Instead of the axe »memcopy«, > I used the hammer »placement new«. Yes, you did. Your program still has undefined behavior. See my other reply to your use of placement new. Not to mention that your container is pretty much useless since it cannot reallocate itself without destroying that fragile type information you so carefully nailed into that memory by placement new. Try growing that container beyond its initial capacity. Same with moving elements. Try sorting that container. Or erasing one of its elements in the middle somewhere... > An OP asked how to put both ints and chars into a list, > and I wanted to show how to mix ints and chars in a list > retaining the type information for each int and each char. Yes, and you've failed. The proper way would be to point the OP to the Boost::variant implementation. >[..] V -- I do not respond to top-posted replies, please don't ask |
| Paavo Helde <myfirstname@osa.pri.ee>: Jun 16 01:46AM -0500 ram@zedat.fu-berlin.de (Stefan Ram) wrote in >::std::cout << sizeof( IntListEntry )<< '\n'; >::std::cout << sizeof( CharListEntry )<< '\n'; > and always got the same value »16«. This is irrelevant. What you are doing is more or less equivalent to: ListEntry x; DerivedClass y; x = y; Hopefully you do not think that by doing this the variable x of base class type changes its type and is now an object of DerivedClass. C++ is a strongly typed language so the type of x does not change when you assign a new value to it. hth Paavo |
| ram@zedat.fu-berlin.de (Stefan Ram): Jun 16 12:55AM >the compiler creates a copy. But just before it starts chiseling that >copy, it needs to convert the original into the type that your container >is declared to store. That's why I eventually resorted to »memcpy« for the copy. But even that did not help! >that original object the derived type, and you're left with the core, >the pit, the bone that represents the complete and fully functional (in >all senses) base class object. Ok, that might explain it. I really should read more about slicing! |
| ram@zedat.fu-berlin.de (Stefan Ram): Jun 16 02:18AM >And when he's fixed that, he can add const to the base class's print so >the derived classes can override it. You all explained to me why it did /not/ work. Now, why /does/ the following work? #include <iostream> #include <ostream> struct B { virtual void p() { ::std::cout << "B\n"; }}; struct D : B { void p() { ::std::cout << "D\n"; }}; int main() { struct B a[ 2 * sizeof( B )]; new( static_cast< void * >( a + 0 ))B(); new( static_cast< void * >( a + 1 ))D(); a[ 0 ].p(); a[ 1 ].p(); } It prints B D That is, it has placed an instance of B and an instance of D into my container, and I can now call the virtual p() with the /run-time type/ B or D instead of the static container type B. |
| ram@zedat.fu-berlin.de (Stefan Ram): Jun 16 02:37AM >Using memcpy to copy C++ objects is like using an axe to perform >appendectomy. Ok, now I got it working here. Instead of the axe »memcopy«, I used the hammer »placement new«. But let me remind you first how it started: An OP asked how to put both ints and chars into a list, and I wanted to show how to mix ints and chars in a list retaining the type information for each int and each char. So now I got a program where I can add the int »1« and the char »'a'« to a list, and then print them still remembering their correct types! I see that this is working here, but I do not know whether it is valid portable C or just working by coincidence. I'd appreciate any comments on how to improve the code keeping it working as intended. Especially the implementation of »push_back« below might be improved. Here's my code: #include <iostream> #include <ostream> #include <string> #include <initializer_list> #include <list> using namespace ::std::literals; struct ListEntry { int i; ListEntry( int const i ): i( i ){} virtual ::std::ostream & print( ::std::ostream & stream )const { return stream }; }; struct IntListEntry : public ListEntry { IntListEntry( int const i ): ListEntry( i ){} virtual ::std::ostream & print( ::std::ostream & stream )const { return stream << ::std::to_string( i ); }}; struct CharListEntry : public ListEntry { CharListEntry( char const c ): ListEntry( static_cast< int >( c )){} virtual ::std::ostream & print( ::std::ostream & stream )const { return stream << static_cast< char >( i ); }}; static inline void push_back ( ::std::list< ListEntry >& list, int const i ) { list.push_back( ListEntry( i )); new( &list.back() )IntListEntry( i ); } static inline void push_back ( ::std::list< ListEntry >& list, char const c ) { list.push_back( ListEntry( c )); new( &list.back() )CharListEntry( c ); } static inline ::std::ostream& operator << ( ::std::ostream & stream, ListEntry & entry ) { return entry.print( stream ); } int main() { ::std::list< ListEntry >list; push_back( list, 1 ); push_back( list, 'a' ); ::std::cout << list.front() << "\n"s; ::std::cout << list.back() << "\n"s; } And it prints: 1 a BTW: Sutter et al. recently emphasized how much faster programs can become when all data is kept in a vector and not spread across memory as in the case of a vector with pointers to data spread in the heap. So, being able to put objects of the same size directly into a polymorphic container might have significant run-time benefits. PS: just added: static_assert( sizeof( ListEntry )== sizeof( IntListEntry ), "sizeof( ListEntry )== sizeof( IntListEntry )" ); static_assert( sizeof( ListEntry )== sizeof( CharListEntry ), "sizeof( ListEntry )== sizeof( CharListEntry )" ); . |
| ram@zedat.fu-berlin.de (Stefan Ram): Jun 16 02:42AM Supersedes: <polymorphism-20150616042656@ram.dialup.fu-berlin.de> [»{ return stream };« --> »{ return stream; }« and PPS] ram@zedat.fu-berlin.de (Stefan Ram) writes: >Using memcpy to copy C++ objects is like using an axe to perform >appendectomy. Ok, now I got it working here. Instead of the axe »memcopy«, I used the hammer »placement new«. But let me remind you first how it started: An OP asked how to put both ints and chars into a list, and I wanted to show how to mix ints and chars in a list retaining the type information for each int and each char. So now I got a program where I can add the int »1« and the char »'a'« to a list, and then print them still remembering their correct types! I see that this is working here, but I do not know whether it is valid portable C or just working by coincidence. I'd appreciate any comments on how to improve the code keeping it working as intended. Especially the implementation of »push_back« below might be improved. Here's my code: #include <iostream> #include <ostream> #include <string> #include <initializer_list> #include <list> using namespace ::std::literals; struct ListEntry { int i; ListEntry( int const i ): i( i ){} virtual ::std::ostream & print( ::std::ostream & stream )const { return stream; }}; struct IntListEntry : public ListEntry { IntListEntry( int const i ): ListEntry( i ){} virtual ::std::ostream & print( ::std::ostream & stream )const { return stream << ::std::to_string( i ); }}; struct CharListEntry : public ListEntry { CharListEntry( char const c ): ListEntry( static_cast< int >( c )){} virtual ::std::ostream & print( ::std::ostream & stream )const { return stream << static_cast< char >( i ); }}; static inline void push_back ( ::std::list< ListEntry >& list, int const i ) { list.push_back( ListEntry( i )); new( &list.back() )IntListEntry( i ); } static inline void push_back ( ::std::list< ListEntry >& list, char const c ) { list.push_back( ListEntry( c )); new( &list.back() )CharListEntry( c ); } static inline ::std::ostream& operator << ( ::std::ostream & stream, ListEntry & entry ) { return entry.print( stream ); } int main() { ::std::list< ListEntry >list; push_back( list, 1 ); push_back( list, 'a' ); ::std::cout << list.front() << "\n"s; ::std::cout << list.back() << "\n"s; } And it prints: 1 a BTW: Sutter et al. recently emphasized how much faster programs can become when all data is kept in a vector and not spread across memory as in the case of a vector with pointers to data spread in the heap. So, being able to put objects of the same size directly into a polymorphic container might have significant run-time benefits. PS: just added: static_assert( sizeof( ListEntry )== sizeof( IntListEntry ), "sizeof( ListEntry )== sizeof( IntListEntry )" ); static_assert( sizeof( ListEntry )== sizeof( CharListEntry ), "sizeof( ListEntry )== sizeof( CharListEntry )" ); . PPS: For simplification I have so far ignore the topic of proper calls of destructors. |
| ram@zedat.fu-berlin.de (Stefan Ram): Jun 16 03:09AM >Yes, you did. Your program still has undefined behavior. See my other >reply to your use of placement new. I will read that again. I am still hoping to find a way to have that observed behavior with portably defined behavior. >Try growing that container beyond its initial capacity. Same with >moving elements. Try sorting that container. Or erasing one of its >elements in the middle somewhere... But in this case, the container is an ::std::list. In the case of an ::std::list, several common operations have guarantees about not invalidating most iterators that are iterators into the list. An ::std::list will not suddenly reallocate itself like an ::std::vector. |
| ram@zedat.fu-berlin.de (Stefan Ram): Jun 16 03:33AM >related). And how are they destroyed? They are destroyed as objects of >type B, which leads to undefined behaviour AFA a[1] is concerned. Not >to mention that a[0] is constructed *twice* and destructed only once But can't both behaviors be repaired? For the first case I can use a virtual destructor, and for the second a manual destructor call. #include <iostream> #include <ostream> struct B { virtual void p() { ::std::cout << "B::p()\n"; } virtual ~B(){ ::std::cout << "~B()\n"; }}; struct D : B { virtual void p() { ::std::cout << "D::p()\n"; } virtual ~D(){ ::std::cout << "~D()\n"; }}; int main() { static_assert( sizeof( B )== sizeof( D ), "" ); B a[ 2 ]; a[ 1 ].~B(); new( a + 1 )D(); // destroy old a[ 1 ] and place new a[ 1 ] a[ 0 ].p(); a[ 1 ].p(); ::std::cout << "exit\n"; } This prints: ~B() B::p() D::p() exit ~D() ~B() ~B() The first »~B()« printed is the manual destructor call. Then come two lines that show polymorphism at work. After »exit« has been printed, the destructor of the second array component is called which calls the destructor of its base class. Then the destructor of the first array component is called. Why does this now still has undefined behavior? |
| You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment