[Top][All Lists]

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

[lmi] Define special member functions inline?

From: Greg Chicares
Subject: [lmi] Define special member functions inline?
Date: Sat, 4 Mar 2017 02:01:07 +0000
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Icedove/45.6.0

I'm working on a fairly large changeset to replace lmi::uncopyable
with explicit "=delete" copy ctors and assignment operators, e.g.:

-#include "uncopyable_lmi.hpp"

 class XYZ final
-    :private lmi::uncopyable <XYZ>
+    XYZ(XYZ const&) = delete;
+    XYZ& operator=(XYZ const&) = delete;

This turns out to be more interesting than I had anticipated. For one
thing, deriving from an "uncopyable" class didn't prevent the compiler
from generating a default ctor, whereas writing
  XYZ(XYZ const&) = delete;
does; but that's not too difficult to deal with.
Then there are oddities like this, in class modal_outlay:
    explicit modal_outlay(yare_input const&);
where a user-defined ctor exists, so the compiler won't generate a
default ctor...but we declare (yet don't define) a default ctor anyway.
If there was a reason for doing that prior to C++98, I've forgotten
what it is; but now that default ctor should simply not be declared.

And then there's this:
  FundData(); // Private, but implemented.
in a class with a public ctor and no obvious factory function. That
reminds me of the old "Can you explain this?" magazine ads for a non-
free 'lint' program. (There is an xml-file factory; the documentation
needs to be improved.)

But there's one fine point I'm not sure about: where should "=default"
special member functions be defined...in a '.cpp' file, as is presently
done in HEAD in this example:

  // about_dialog.hpp
   ~AboutDialog() override;

  // about_dialog.cpp
   AboutDialog::~AboutDialog() = default;

...or in the header, patching the above code like this:

+   ~AboutDialog() override = default;
-   ~AboutDialog() override;
-   AboutDialog::~AboutDialog() = default;

I knew the One True Answer in the 1990s: we might forget to pair new
with delete, or new[] with delete[], so we used a leak-testing library
like mpatrol, and to avoid confusing mpatrol when memory is allocated
on one side of a DLL boundary and freed on another, we wrote ctors and
the dtor out of line, in the same TU (unless all were inline in the

But it's not necessarily the 1990s anymore, so is that concept now
mumpsimus, or still sumpsimus?

I find little recent commentary in its favor, but Boris Kolpackov was
recommending it as recently as 2012:

| if your object is part of a DLL interface and the DLL and executable
| use different runtime libraries, then you will run into trouble if
| your object allocates dynamic memory using the DLL runtime (e.g., in
| a non-inline constructor) but frees it using the executable runtime
| (e.g., in an inline destructor). By defining the destructor non-inline,
| we can make sure that the memory is allocated and freed using the same
| runtime.

Meyers, EffC++3, item 30, cautioned against inline ctors and dtors, but
that was 2005, and his concern was the not-superficially-obvious size
of the default implementation in derived classes. (That's not really
relevant to our question, but I can't find anyone else who addresses
this subject at all.)

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

AFAICT, the case against this old practice isn't very strong either.
If a ctor is defined out of line, even as "=default", then it becomes
non-trivial and our classes aren't PODs; but is there any good reason
to care about that? (We aren't copying them with memmove(), e.g.) And
do modern-day optimizing compilers apply the as-if rule so liberally
that this practice is unlikely to achieve its intended result anyway?
(I would guess that the intention is still realized.)

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

BTW, if we do change it, then 'wx_new.hpp'...

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

...and all its baggage might simply be dropped.

reply via email to

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