Tuesday, June 16, 2015

Digest for comp.lang.c++@googlegroups.com - 25 updates in 5 topics

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: