Thursday, July 19, 2018

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

Thiago Adams <thiago.adams@gmail.com>: Jul 19 07:06AM -0700

Something I would like to see in C++ is the
idea of "destructor elision".
 
Similarly of c++ copy elision, where the compiler
is allowed to change the observed behavior, I would
like to give the compiler the chance to remove the
destructor call and change the observable behavior.
 
Basically this feature gives us optimization after
to move objects.
 
 
std::unique_ptr<int> p1 = std::make_unique<int>();
std::unique_ptr<int> p2;
p2 = std::move(p1);

in this case, the compiler would be allowed to remove
the destructor call of the p2.
Thiago Adams <thiago.adams@gmail.com>: Jul 19 07:17AM -0700

On Thursday, July 19, 2018 at 11:06:17 AM UTC-3, Thiago Adams wrote:
> p2 = std::move(p1);
 
> in this case, the compiler would be allowed to remove
> the destructor call of the p2.
 
Correction: (p1 - not p2)
 
..In this case, the compiler would be allowed to remove
the destructor call of the p1.
Thiago Adams <thiago.adams@gmail.com>: Jul 19 07:38AM -0700

On Thursday, July 19, 2018 at 11:17:18 AM UTC-3, Thiago Adams wrote:
 
> Correction: (p1 - not p2)
 
> ..In this case, the compiler would be allowed to remove
> the destructor call of the p1.
 
 
This feature also would allow us to call destructors
before the end of scope.
 
Sample of scope created to call the destructor:
 
//...
{
Lock lock;
//..something
}
//...
 
We could write:
 
//...
Lock lock;
//..something
std::destroy(lock); //move to nothing
//...
 
 
and them this feature give us the same control
we have in manual C code, plus the guarantee
of RAII.
Bo Persson <bop@gmb.dk>: Jul 19 08:19PM +0200

On 2018-07-19 16:06, Thiago Adams wrote:
> p2 = std::move(p1);
 
> in this case, the compiler would be allowed to remove
> the destructor call of the p1.
 
You know, perhaps it already does that?
 
Here is the result on MSVC 2017:
 
 
#include <memory>
int main()
{
00007FF7893D1000 sub rsp,28h
std::unique_ptr<int> p1 = std::make_unique<int>();
00007FF7893D1004 mov ecx,4
00007FF7893D1009 call operator new (07FF7893D1024h)
00007FF7893D100E xor ecx,ecx
00007FF7893D1010 mov dword ptr [rax],ecx
std::unique_ptr<int> p2;
p2 = std::move(p1);
 
}
00007FF7893D1012 lea edx,[rcx+4]
00007FF7893D1015 mov rcx,rax
00007FF7893D1018 call operator delete (07FF7893D1060h)
00007FF7893D101D xor eax,eax
00007FF7893D101F add rsp,28h
00007FF7893D1023 ret
 
 
What destructor call do you want to remove?
 
 
Bo Persson
bitrex <user@example.net>: Jul 19 01:32PM -0400

> bothered about calling free then just use alloca() and allocate on the stack.
> No free() required though there may be limits on how much memory the OS or
> CPU will allow.
 
alloca() is non-portable, and some implementations are bugged. "May be
limited" in practice means "Always by default very limited on all modern
PC architectures and OSes."
 
bitrex <user@example.net>: Jul 19 01:42PM -0400


>> So removing something that has no cost is a sound engineering decision?
 
> There's no such thing as no cost in creating and destructing objects no matter
> how slimline they may be and smart pointers are objects.
 
If a unique_ptr object has no persistent state other than the wrapped
pointer itself, which is the same persistent state the equivalent
smart-pointer-free code would have, and the rest of the object's
behavior is simply no different than the effect of writing "new" and
"delete" at the appropriate times (or malloc/free in C) then that is
precisely what the compiler will do and that's what the compiler will
generate.
 
What sense would it make to do anything else?
bitrex <user@example.net>: Jul 19 01:46PM -0400


>> So removing something that has no cost is a sound engineering decision?
 
> There's no such thing as no cost in creating and destructing objects no matter
> how slimline they may be and smart pointers are objects.
 
You're laboring under the assumption that every time you write Object
object; in your code the compiler is actually producing code to execute
constructors (that may do nothing at all), jump into destructors (that
may do nothing at all), making stack allocations, assigning-immediate
class member variables, running all around doing this and that. Every time.
 
That isn't how modern optimizing compilers work.
bitrex <user@example.net>: Jul 19 01:56PM -0400


>> So removing something that has no cost is a sound engineering decision?
 
> There's no such thing as no cost in creating and destructing objects no matter
> how slimline they may be and smart pointers are objects.
 
It's entirely possible to write some 250 line class or structure which
compiles down to just a couple of assembly instructions, like say a load
immediate and a move of something from one memory address to another.
 
"Why in the world would I write all that code to accomplish two assembly
instructions?" Simple, the point of all that code was for your benefit,
not the computer's instruction set architecture. That is to say the code
allows the compiler to figure out exactly what memory addresses to use
for that single load and move in every context you need, automatically.
Instead of you having to write it out 273 times by hand.
Bart <bc@freeuk.com>: Jul 19 06:58PM +0100

On 19/07/2018 18:46, bitrex wrote:
> may do nothing at all), making stack allocations, assigning-immediate
> class member variables, running all around doing this and that. Every time.
 
> That isn't how modern optimizing compilers work.
 
But they /might/ do all that?
 
Or if they don't, they might be expending effort in assembling all those
operations, and then expend more effort in figuring out how to get rid
of them again. And then people complain about build times.
 
--
bart
boltar@cylonHQ.com: Jul 19 08:41AM

On Wed, 18 Jul 2018 10:13:54 -0700 (PDT)
 
>> See above.
 
>It was removed from the second version of the C standard, in 1999, so that
>doesn't make much difference.
 
Still valid in all C compilers.
jameskuyper@alumni.caltech.edu: Jul 19 04:32AM -0700


> >It was removed from the second version of the C standard, in 1999, so that
> >doesn't make much difference.
 
> Still valid in all C compilers.
 
You've checked every single C compiler ever written? Impressive - I
wouldn't know how to even begin such a review.
There's no point in discussing what non-conforming implementations
of C or C++ do - there's no limits on the possibilities. I'm only
interested in discussing the behavior of compilers when invoked in
a mode that fully conforms to some version of the relevant standard,
and unless otherwise specified, that's the current version. Since
1999, neither the current C standard nor the current C++ standard
has supported implicit int.
boltar@cylonHQ.com: Jul 19 01:17PM

On Thu, 19 Jul 2018 04:32:41 -0700 (PDT)
 
>> Still valid in all C compilers.
 
>You've checked every single C compiler ever written? Impressive - I
>wouldn't know how to even begin such a review.
 
Both gcc and clang require a command line switch to strictly conform to the C99
standard and MSVC++ doesn't even support C99 properly anyway. The de facto
C standard is still C89 and probably always will be.
jameskuyper@alumni.caltech.edu: Jul 19 08:50AM -0700


> Both gcc and clang require a command line switch to strictly conform to the C99
> standard and MSVC++ doesn't even support C99 properly anyway. The de facto
> C standard is still C89 and probably always will be.
 
So "all C compilers" is equivalent to "gcc, clang, and MSVC++"? Wow - what a simple world you live in! Mine's a bit bigger and more complicated than yours. How many different countries are there in your world? How many different languages? Religions? How small is it, really?
David Brown <david.brown@hesbynett.no>: Jul 19 07:28PM +0200

>>>>> the "landing pads" and tables state->landing pads.
 
>>>> Yes, it trades some disk space for speed, but the size of executables
>>>> does not interest most people nowadays (except some nutwits in this group).
 
It interests embedded programmers too. If you have a fixed size of code
flash on a device, and your program is nearing that limit, size begins
to matter a great deal.
 
Code size can also be relevant in terms of cache performance - less code
means higher hit ratios. Of course, this does not matter for things
like exception tables - only for the commonly executed code.
 
> thinking "that's the most pointy haired thing I've every
> heard of!". Come to this talk and learn how this simple
> metric provides surprisingly strong counter pressure to complexity.
 
Binary size deltas can be an interesting measure for code reviews - they
say something about how much of the program has changed. That doesn't
mean that keeping binary sizes small is a useful goal in itself -
keeping /complexity/ low is the aim.
Thiago Adams <thiago.adams@gmail.com>: Jul 19 10:54AM -0700

On Wednesday, July 18, 2018 at 10:05:07 AM UTC-3, Thiago Adams wrote:
...
> It must have some overhead in size because it has to build
> the "landing pads" and tables state->landing pads.
 
Something interesting to compare as well,
is the code generated when we have return points
in the middle of the code.
 
int F(int i)
{
if (i > 0)
{
X x;
if (i < 2) return 0;
Y y;
if (i < 3) return 0;
Z z;
if (i < 4) return 0;
}
return 0;
}
 
Differently from exceptions, everything is static.
I believe the size can also be affected having more
return points in the middle of the code and the compiler
will try to reuse some states.
 
Instead of "landing pad" in this case we have
"exit points" that would call some destructors and
not the others.
 
I would like to understand how compilers works to generate
this code (in case someone has links about this). Maybe is
something like an DFA with minimization.
boltar@cylonHQ.com: Jul 19 08:44AM

On Wed, 18 Jul 2018 19:14:18 +0200
>Templates: none
>Iostream: none
>Multiple inheritance: none
 
Does multiple inheritance get used much anyway? I can count the number of times
I've seen it in my 20+ year career on one hand. To me it seems like one of
those features that should be useful but almost never is because composition
or aggregation are almost always a better solution.
Rosario19 <Ros@invalid.invalid>: Jul 19 12:07PM +0200

On Thu, 19 Jul 2018 08:44:24 +0000 (UTC), boltar wrote:
>><quote>
>>So we limited our code to "Embedded C++�€? constructs
>>Exceptions: none
 
this seems to me ok... each function return error if can, and each
that call see if some error (if this is important)
 
>>Templates: none
 
where templates are the problem? the code that has <int> is traslated
as it is one int type in what i understand
 
>>Iostream: none
 
than one has to write his own routines... seems ok, if one not
introduce bugs (one can rewrite iostream eliminate their i presume bug
(can not possible to check for sucessfull/wrong input or remember it
wrong? because i rewrote getint getdouble with cin input etc))
 
possible now make error more than usual because some grappa
 
>>Multiple inheritance: none
 
i don't know the meaning...
 
Rosario19 <Ros@invalid.invalid>: Jul 19 12:20PM +0200

On Thu, 19 Jul 2018 12:07:45 +0200, Rosario19 wrote:
 
>>>Exceptions: none
 
>this seems to me ok... each function return error if can, and each
>that call see if some error (if this is important)
 
"exceptions" could be ok in some exceptional case
but i could be wrong on this (and in all the remain)
this is just my opinion
scott@slp53.sl.home (Scott Lurndal): Jul 19 02:21PM

>would have expected that all the emacs afficionados would have
>automated refactoring implemented in elisp, but nope. They have
>essentially the same environment I had in 1988.
 
If it works for them, why should they change?
scott@slp53.sl.home (Scott Lurndal): Jul 19 02:29PM

>I've seen it in my 20+ year career on one hand. To me it seems like one of
>those features that should be useful but almost never is because composition
>or aggregation are almost always a better solution.
 
From a current project:
 
class c_xyz: public c_device,
public c_tx_sink_interface,
public c_tx_src_interface,
public c_rx_sink_interface,
public c_rx_src_interface,
public c_memory_accessors
 
 
class c_zyx: public c_device,
public c_thread,
public c_memory_accessors,
public c_pci_callback
 
 
Granted in most cases the secondary base classes are
pure virtual interface classes (c_thread is an exception
to that - it automatically creates a thread and invokes the
derived class 'run' function from that thread after the
derived class constructor completes).
boltar@cylonHQ.com: Jul 19 03:08PM

On Thu, 19 Jul 2018 14:29:52 GMT
> public c_rx_src_interface,
> public c_memory_accessors
 
>class c_zyx: public c_device,
 
I hope your real class names are better than c_xyz and c_zyx!
 
>to that - it automatically creates a thread and invokes the
>derived class 'run' function from that thread after the
>derived class constructor completes).
 
And if the derived class constructor doesn't complete but throws an exception?
Presumably you have to tell the thread not to bother in the exception handler.
That seems a rather obtuse and error prone way to obtain a new thread.
scott@slp53.sl.home (Scott Lurndal): Jul 19 03:43PM

>> public c_memory_accessors
 
>>class c_zyx: public c_device,
 
>I hope your real class names are better than c_xyz and c_zyx!
 
Yes, they were altered to avoid exposing proprietary designators.
 
>>derived class 'run' function from that thread after the
>>derived class constructor completes).
 
>And if the derived class constructor doesn't complete but throws an exception?
 
They cannot, by design.
gof@somewhere.invalid (Adam Wysocki): Jul 19 08:09AM


> - Enforce at compile time that an object can only be instanciate on the stack. This is to implement a "safe" dynarray type like object.
 
No idea here.
 
> - Enforce at compile tiem that an object can only be instanciate on the heap. This is to ensure That no "Big" object are instanciated on the stack.
 
The only solution I see is creating a "wrapper" class that would
instantiate the desired class on the heap even if the wrapper is
instantiated on the stack.
 
--
[ Adam Wysocki :: Warsaw, Poland ]
[ Email: a@b a=grp b=chmurka.net ]
[ Web: http://www.chmurka.net/ ]
Paavo Helde <myfirstname@osa.pri.ee>: Jul 19 04:11PM +0300


> I would like to create a class/template but want to prevent at compile time where it can live. Seems like type_traits or the upcoming concepts cannot be use for that.
 
This is against the general idea of modularity. The idea is to provide
building blocks which can be combined flexibly by the users of those
building blocks. Restricting the object placement without a very good
reason limits this flexibility and should be enforced by project
guidelines/policies, not by the software libraries themselves.
 
> I have two scenarios:
> - Enforce at compile time that an object can only be instanciate on the stack. This is to implement a "safe" dynarray type like object.
 
What do you mean by "safe dynarray"? What is "unsafe dynarray"?
 
One option is to define operator new and operator new[] for the class as
=delete. However, the client code can easily circumvent this by placing
the class inside a wrapper.
 
Anyway, I do not see a reason why something which can be placed on the
stack should not be allocated dynamically. In some very rare
circumstances one might even want to allocate a mutex lock dynamically.
 
> - Enforce at compile tiem that an object can only be instanciate on the heap. This is to ensure That no "Big" object are instanciated on the stack.
 
I have no idea what could be such a Big object so that its size is huge
and known at compile time already. A table of possible Rubic cube
states? Seems more like a non-existing issue.
 
Enforcing on heap might be useful for polymorphic types which are
supposed to be accessed by pointers only. But then the issue already
mostly resolves itself, if the client code wants to use an interface
taking a smartpointer to object, it first needs to have one, and for
that it needs to construct the object dynamically, for example by
std::make_unique().
 
This can even be enforced a bit by defining a private destructor:
 
#include <memory>
 
class A {
private:
~A() {}
friend struct std::default_delete<A>;
public:
// ...
};
 
An A can now be created with std::make_unique().
 
For std::make_shared it seems to become more complicated unfortunately
and I haven't found a good solution, other than providing a custom
smartpointer class which does not allow creation from a raw pointer, and
providing static factory methods for creating the objects dynamically
only, as suggested by other posts.
 
> I guess being able to enforce/prevent being global could be usefull. Example an object you don't want global because its constructure depends on some stuff initialised in the main().
 
Mutable globals are indeed evil and should be avoided. In this example
it seems quite easy to achieve, the object constructor should just take
"the stuff initialized in the main()" as a parameter (and of course the
"stuff" itself should be only created when initialized, i.e. not a global).
 
 
> Does this idea make any kind of sense?
 
Not much, to be honest. I am even doubting my own long-winded efforts to
suppress stack initialization.
gof@somewhere.invalid (Adam Wysocki): Jul 19 12:25PM


> Somebody posted on comp.lang.asm.x86 an algorithm for fizz buzz.
 
There are many language tricks and hacks to make the solution interesting,
but I prefer solutions that are readable and simple enough to do the task.
 
The cleanest (definitely not the shortest) solution I could come up with
(in C++):
 
#v+
#include <boost/format.hpp>
#include <iostream>
 
static const unsigned MIN_NUMBER = 1;
static const unsigned MAX_NUMBER = 100;
 
/**
* \brief Check if value is divisible by another value
*
* \retval true Dividend is divisible by divisor
* \retval false Value is not divisible by divisor
*/
 
static inline bool isDivisible(unsigned dividend, unsigned divisor)
{
return !(dividend % divisor);
}
 
/**
* \brief Make word according to FizzBuzz rules
*
* Returns word according to FizzBuzz rules, that is:
*
* - if value is divisible by 3 and not by 5, returns "Fizz"
* - if value is divisible by 5 and not by 3, returns "Buzz"
* - if value is divisible both by 3 and 5, returns "Fizz Buzz"
* - as the last resort, returns string containing the value
*
* \param number Number to check
* \return String according to FizzBuzz rules
*/
 
static std::string makeWord(unsigned number)
{
std::string word;
 
if (isDivisible(number, 3))
word += "Fizz";
 
if (isDivisible(number, 5))
{
if (!word.empty())
word += " ";
 
word += "Buzz";
}
 
if (word.empty())
word = boost::str(boost::format("%u") % number);
 
return word;
}
 
int main()
{
for (unsigned i(MIN_NUMBER); i <= MAX_NUMBER; ++i)
{
const std::string suffix(i != MAX_NUMBER ? ", " : "\n");
std::cout << makeWord(i) << suffix;
}
 
return 0;
}
#v-
 
--
[ Adam Wysocki :: Warsaw, Poland ]
[ Email: a@b a=grp b=chmurka.net ]
[ Web: http://www.chmurka.net/ ]
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: