lmi
[Top][All Lists]
Advanced

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

Re: [lmi] Naming certain overloaded functions


From: Greg Chicares
Subject: Re: [lmi] Naming certain overloaded functions
Date: Thu, 26 Oct 2006 15:20:24 +0000
User-agent: Thunderbird 1.5.0.4 (Windows/20060516)

On 2006-10-26 13:19 UTC, Vadim Zeitlin wrote:
> On Thu, 26 Oct 2006 13:00:22 +0000 Greg Chicares <address@hidden> wrote:

[inserted to clarify discussion below, and slightly simplified:]

class streamable {
  public:
    virtual void write(xml_lmi::Element&) const = 0;
// No other 'write' function in this base class.
};

class Ledger: virtual public streamable {...}

> GC> Class Ledger has [realigned for emphasis]:
> GC>     virtual void   write(xml_lmi::Element&) const;
> GC>     void           write(std::ostream& os) const;
> GC>     void        do_write(xml_lmi::Element&, bool light_version) const;
> GC>                 ???^^^^^
> GC> Is there any objection to changing the last to
> GC>     void           write(xml_lmi::Element&, bool light_version) const;
> GC> so that they're all overloads of 'write'?
> 
>  It's in general a bad idea to have a virtual function in a set of
> overloads. This is because if [one of] the virtual methods is overloaded in
> a derived class, it hides all the other overloads (this is known as a
> "virtual function hiding problem" in C++) making it impossible to use them.

I thought the problem was this:

struct Base {
  virtual foo(int);
  virtual foo(double);
};

struct Derived: Base {
  virtual foo(int); // Now Base::foo(double) is hidden.
};

I had taken it as a theorem that this problem can be avoided by
implementing all virtuals in the derived class; and as a corollary
that it can be avoided by making all virtuals pure in the base class.
Is that wrong?

> There are, of course, workarounds:

Which, as you point out, are better avoided.

>  Which is why I strongly prefer to make write() functions non-virtual and
> have a single virtual do_write() usually. But, of course, here this is
> impossible as we inherit the virtual write() from the base class.

We can change that base class. I see you post an idea below, so
let me sketch mine here before I examine yours:

class streamable {
  public:
    void write(xml_lmi::Element&) const; {/* calls do_write() */}
  private:
    virtual void do_write(xml_lmi::Element&) const = 0;
};

class Ledger: public streamable {
  private:
    virtual void do_write(xml_lmi::Element&) const {/* implement here */}
};

I've done that in other places, but it seemed like overkill here--I
don't find the purpose given in this paragraph quoted from Kanze:

http://groups.google.com/group/comp.lang.c++.moderated/msg/ccfdf0e8beb6f5bb
|
| >>my usual use is not for customizing.  The base class is conceptually
| >>an interface, with *NO* behavior of its own.  The non-virtual public
| >>functions are there to enforce the contract specified by the
| >>interface, or to instrument the interface, and for no other reason.

In this thread, I think Henney considered Sutter to be advocating
too-broad use of NVI, and I've tried not to use it promiscuously
since I read that.

>  Summarizing all this, I'd propose:
> 
>       virtual void write(xml_lmi::Element&) const;
>       void output(std::ostream& os) const;
> 
>       // in private part
>       void do_write(xml_lmi::Element&, ...) const;

I believe it's more usual to use a 'do_' prefix on the pure virtual.
When I see Derived::do_foo(), I expect
  Base::foo();    // public and nonvirtual
  Base::do_foo(); // virtual and nonpublic
That's what 'do_'- means to me, and I think I'm not alone. If it had
been 'perform_write' or 'write_selectively', I wouldn't have noticed.

But wait--are we talking about the same thing at all? Rereading
this, I think your

> impossible as we inherit the virtual write() from the base class.

means not that you wish Base::write() were nonvirtual, but
rather that Base::write() is not overloaded, and shouldn't be.

Am I understanding this correctly now?

> I think keeping do_write() name makes sense here because, although write()
> is virtual and it is not, it's still the real implementation of write()
> which is just a wrapper here. But if there is another convention for
> calling such kind of functions in LMI already (e.g. write_impl() or
> write_internal() or whatever) then it should be used, of course.

I don't have any convention in mind. Anything but 'do_'- or 'Do'-.
Or, I guess, -'impl' because that seems to imply envelope-letter
(I can't bring myself to use Sutter's term that means 'acne lesion').
As for just plain 'write' with no prefix...

[reordered]
> But then
> in this case it just doesn't seem like a good idea to have the std::ostream
> overload anyhow: the 2 functions do quite different things AFAICS so it's
> confusing that they have the same name.

    virtual void write(xml_lmi::Element&) const;
            void write(std::ostream&    ) const;

They both write *this to somewhere. One writes to an xml element;
the other, to a stream. Why is that a bad use of overloading?

>  Finally, completely unrelated to the overloaded functions names, but still
> related to this do_write() function, I'd like to also change its
> parameters. This is my pet peeve so please bear with me if you heard it
> before (Evgeniy definitely did) but using bool parameters for things other
> than obvious on/off switches is simply evil, please see my attempt to
> explain why here:
> 
>       http://www.wxwidgets.org/develop/standard.htm#no_bool_params

Thanks: I don't think I'd seen that document before.

I completely agree. Recently, I began to change the interface of
class calendar_date--but in another trunk, so lmi's HEAD and
branches still have this:

  calendar_date add_years
    (calendar_date const& date
    ,int                  n_years
    ,bool                 is_curtate
    );

  int attained_age
    (calendar_date const& birthdate
    ,calendar_date const& as_of_date
    ,bool                 use_age_nearest_birthday
    );

So if I want to know how old you'll be N years hence, it's
  attained_age(birthdate, add_years(today, N, XXX), YYY);
where bool parameters XXX and YYY are some combination of
'true' and 'false'. I made mistakes: which of XXX and YYY
should I change to get the age as of the nearest birthday?
And does 'false' mean 'curtate' or not? That's the problem
you explain as "Readability".

As for your "Extensibility" issue: here, it turns out that
there are half a dozen meaningful values for parameter
    bool use_age_nearest_birthday
even though we've only needed two...so far...except that I
noticed a piece of code that smelled bad, and making it
produce a testable error required implementing a third.

Another advantage of enumerators is that they're greppable.

> So I strongly propose that do_write() signature be changed to
> 
>       enum xmlStyle
>       {
>               xmlStyle_Light,
>               xmlStyle_Full
>       };
> 
>       void do_write(xml_lmi::Element&, xmlStyle style) const;

I embrace the principle, but would ask you to write it this way:

enum enum_xml_style
    {e_xml_light
    ,e_xml_heavy
    };

to follow a convention that, though undocumented [I'm editing
things like this into 'style.html' as they come up] and perhaps
not ideal, is extremely widespread in 'lmi'. It's
  enum enum_* {e_*, e_*};
that's important; longer, more-descriptive enumerator-names like
  e_minimal_dataset
are often a good idea, and can make it unnecessary to repeat a
lexeme redundantly: i.e., I wouldn't insist on
  enum enum_xml_* {e_xml_*, e_xml_*};
                     ^^^^     ^^^^
at all.




reply via email to

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