lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] Define special member functions inline?


From: Vadim Zeitlin
Subject: Re: [lmi] Define special member functions inline?
Date: Sat, 4 Mar 2017 03:42:35 +0100

On Sat, 4 Mar 2017 02:01:07 +0000 Greg Chicares <address@hidden> wrote:

GC> Then there are oddities like this, in class modal_outlay:
GC>   public:
GC>     explicit modal_outlay(yare_input const&);
GC>   private:
GC>     modal_outlay();
GC> where a user-defined ctor exists, so the compiler won't generate a
GC> default ctor...but we declare (yet don't define) a default ctor anyway.
GC> If there was a reason for doing that prior to C++98, I've forgotten
GC> what it is; but now that default ctor should simply not be declared.

 Yes, definitely, it was never necessary to declare it and I think this
could only have ever been done to work around a bug in some ancient
standard library implementation.

GC> And then there's this:
GC>   FundData(); // Private, but implemented.

 I thought this would be something even more mysterious due to the presence
of the comment seemingly indicating that it was done intentionally, but a
quick "git blame" (or, rather, ":Gblame" in Vim) later it became clear that
the comment was added by you when the old TFundData class was renamed to
FundData, but the ctor itself had existed even before that commit and its
origins are lost in dark times before the beginning of git history. So I
guess this was again necessary to allow using std::vector<FundData> or
something like this.

GC> But there's one fine point I'm not sure about: where should "=default"
GC> special member functions be defined...in a '.cpp' file
<...>
GC> or in the header

 It may seem unhelpful at first, but my answer is to apply the same logic
as you would apply when defining the special function doing the same thing
as the compiler-generated version does explicitly. Which, for me, means
(almost) always defining them in the header, with some rare exceptions,
e.g. when the class contains a (std::unique_) pointer to an object of
incomplete class.

GC> I knew the One True Answer in the 1990s: we might forget to pair new
GC> with delete, or new[] with delete[], so we used a leak-testing library
GC> like mpatrol, and to avoid confusing mpatrol when memory is allocated
GC> on one side of a DLL boundary and freed on another, we wrote ctors and
GC> the dtor out of line, in the same TU (unless all were inline in the
GC> header).
GC> 
GC> But it's not necessarily the 1990s anymore, so is that concept now
GC> mumpsimus, or still sumpsimus?

 I think even in the 1990s using different versions of run-time libraries
for the main application and the DLL was always asking for trouble because
it's so simple to accidentally use a wrong [un]allocation function. So
while the reason above would still apply if you did this today just as it
applied back then, I think this shouldn't be done in the first place.

GC> Meyers, EffC++3, item 30, cautioned against inline ctors and dtors, but
GC> that was 2005, and his concern was the not-superficially-obvious size
GC> of the default implementation in derived classes.

 I have to admit that I don't understand it at all.

GC> At least in the old days, an inline function would be defined in every
GC> TU that needed it, meaning extra work for the compiler, and then the
GC> linker would have to do extra work to remove the duplicates. I'm not
GC> sure that matters much with contemporary hardware; but if it helps at
GC> all, then...it's helpful.

 I'd suggest measuring this effect before declaring it helps because I'm
reasonably certain that any potential benefit would be so tiny as to be
unmeasurable.

GC> AFAICT, the case against this old practice isn't very strong either.

 Using "= default" has 2 benefits: first, it makes the code more clear,
which is the most important one, as far as I'm concerned, but, second, it
allows the compiler to generate more optimal code because it can make
assumptions about the code it synthesizes on its own that it can't make
about user-defined functions. Granted, I didn't actually measure any real
benefit resulting from this neither, but it is supposed to exist if you
believe presentations from several people well-known in C++ community,
including some people working on the compiler (albeit clang and not gcc).
And, of course, the compiler can only benefit from these assumptions
outside of the source file where the class is defined when the special
function is defined inline, so I would define all defaulted special
functions inline to give it a chance of generating better code.

GC> Actually, lmi practice has been to define the dtor and all ctors
GC> out-of-line whenever any one of them is defined out-of-line. Is there
GC> any reason to change that convention now?

 I don't see any good reason to define the ctors and dtor in the same
place. My personal rule is to define all defaulted special functions inline
(because of the above efficiency considerations and also because it avoids
unnecessary repetition) and also define trivial ctors inline as well, i.e.
apply the same heuristics for determining whether a ctor should be inline
as is applied to any other function, without taking into account other
ctors or dtor.


GC> BTW, if we do change it, then 'wx_new.hpp'...
GC> 
GC> /// When wx is used as an msw dll, memory is allocated and freed
GC> /// across dll boundaries, and that causes mpatrol to emit spurious
GC> /// diagnostics.
GC> ///
GC> /// To work around this problem, build these functions as a separate
GC> /// dll, and use 'new(wx)' to allocate memory that will be freed by
GC> /// wx--for instance, a frame window that's created in an application
GC> /// but (unavoidably) freed by a wx dll. The sole purpose of this
GC> /// workaround is to avoid spurious diagnostics; it is not suggested
GC> /// that this is a good way to manage memory.
GC> 
GC> ...and all its baggage might simply be dropped.

 Actually, I rather like the idea of using a separate allocator for wx
objects as it clearly indicates those "new"s that must not be matched by
"delete"s and allows us to distinguish them from all the others, which,
ideally, we should get rid of completely because a well-written C++(11)
program should ideally contain no naked new/delete at all. But it's a
rather theoretical/ philosophical viewpoint and I'm not sure if it really
passes cost/benefit analysis, so I'd understand if you decided to get rid
of all this stuff too.

 Regards,
VZ


reply via email to

[Prev in Thread] Current Thread [Next in Thread]