2

According to Why define a Java object using interface (e.g. Map) rather than implementation (HashMap), I know I should declare most abstract type when possible, for example, suppose I'm using an UI engine which has BlinkLabel that has a speific method setBlinkTime(), and extends from Label, they used to display some text on the screen:

Original version:

BlinkLabel* label1=BlinkLabel::create("text1",32);
label1->setColor(Color.RED);
label1->setBlinkTime(0.5);
this->addChild(label1);

BlinkLabel* label2=BlinkLabel::create("text2",32); //use default blink rate, so no need to call setBlinkTime()
label2->setColor(Color.BLUE);
this->addChild(label2);

"Declare most abstract type" version:

BlinkLabel* label1=BlinkLabel::create("text1",32);
label1->setColor(Color.RED);
label1->setBlinkTime(0.5);
this->addChild(label1);

Label* label2=BlinkLabel::create("text2",32); //use default blink rate, so no need to call setBlinkTime()
label2->setColor(Color.BLUE);
this->addChild(label2);

which label2 doesn't need to call setBlinkTime(), so it should declare as Label instead of BlinkLabel, fulfilling "Declare most abstract type" guideline. However, according to Does auto make C++ code harder to understand?, I should use auto when possible:

auto* label1=BlinkLabel::create("text1",32);
label1->setColor(Color.RED);
label1->setBlinkTime(0.5);
this->addChild(label1);

auto* label2=BlinkLabel::create("text2",24); //use default blink rate, so no need to call setBlinkTime()
label2->setColor(Color.BLUE);
this->addChild(label2);

The problem is, when using "auto", and then I call the setBlinkTime() at label2 unnecessarily (eg: copy code from label1 but forgot to delete it):

label2->setBlinkTime(0.5);

The "auto" version would compile, which is the thing that "declare most abstract type" wants to avoid : avoid call subclass methods unnecessarily by avoid compiling successfully.

So my question is, "use auto" and "declare most abstract type", which guideline has higher priority?

5
  • I'd like to point out that this is highly unidiomatic C++, since it's full of raw pointers. Qt does that for legacy reasons. But in idiomatic, modern C++, "declare most abstract type" is not applicable, being almost always overridden by "put your objects on the stack". And also, C++ usually just doesn't feature all those interfaces that you can use; far more classes are just concrete classes without any implemented interfaces. Commented Sep 3 at 7:40
  • @SebastianRedl a modern UI framework would have dynamically allocated widgets. The legacy part is only the owning raw pointers
    – Caleth
    Commented Sep 3 at 10:42
  • @Caleth Would it always? Maybe your custom widget class has its members as inline members instead of dynamically allocating each one. Maybe you have a declarative UI with a custom language a la QtQuick, and while the widgets are allocated, you don't deal with them in terms of single objects. Maybe you have a function call interface like IMGUI. There are options other than having a bunch of (smart) pointers to widget classes. Commented Sep 3 at 10:46
  • @SebastianRedl I was just pointing out that there are modern UI frameworks that are similar to Qt, but don't have owning raw pointers. I wasn't implying that was the only way
    – Caleth
    Commented Sep 3 at 11:12
  • "I know I should declare most abstract type when possible" - that's taking the guideline too far. You shouldn't do that everywhere. Declare the most abstract type in places where you accept dependencies or external data (like in parameter lists of ctors and methods), if you want to pass in different subtypes. That could be via a base class–typed pointer (runtime polymorphism), or maybe by accepting an abstraction that supports multiple distinct types, like an iterator. In internal implementation code, like in short function bodies, for local variables and such, there's little reason to do so. Commented Sep 3 at 21:50

3 Answers 3

3

I know I should declare most abstract type when possible

I think this rule-of-thumb (it is actually nothing more than that!) is vastly overstated. Karl Bielefeld's answer to that first linked question already contains the answer to your question. He wrote:

However, there's a trade off, because you're artificially limiting the code that can take your object as a parameter. Say there's a function somewhere that requires a HashMap for some reason. If you return a Map, you can't pass your object into that function. You have to balance the likelihood of sometime in the future needing the extra functionality that's in the more concrete class with the desire to limit coupling and keep your public interface as small as possible.

Making such trade-off decisions is nothing the keyword auto can do automatically, you have to make the decision which type you need, with your knowledge about the overall context.

For example,

  • if you know in the context you are working using a BlinkLabel as a BlinkLabel, with a certain chance to use specific methods of this class now or in the near future, just use auto (as it gives you a BlinkLabel variable).

  • if you need a label in your GUI, but are not sure yet whether this label will stay a BlinkLabel in the next version of your program, or if it might become a ColorLabel, an ImageLabel or a TextLabel soon, and in case the common base type Label is sufficient for what you need now, declare label2 as Label*.

That's perfectly in line with what Herb Sutter wrote in his answer to the second question you linked to:

Write an explicit type (rather than auto) only when you really want to explicitly commit to a type.

So when you want to "commit to the type" Label instead of "BlinkLabel", then write Label, otherwise use auto.

Let me add I have done C++ programming long ago before auto was introduced into the language. At that time, it was probably more obvious that picking the most useful type deliberately is always the responsibility of the programmer, not of the language, not of the ecosystem, and not of some braindead checklist. The availability of auto hasn't changed this.

1
  • 2
    It is probably interesting to look at languages which have far more pervasive type inference for far longer than C++, not only in the language but in the culture surrounding those languages. Examples are languages in the ML family of languages (Standard ML, OCaml, F#, …) and generally most statically-typed functional languages (Haskell, …), as well as newer object-oriented or "post-functional" languages (Scala). Even in those languages, where type inference is much more widespread, the community will generally have explicit annotations in places, to override the type inferred by the language Commented Sep 3 at 7:53
1

Assuming heap allocation.

Minimize entropy

Do irreversible actions at last possible moment.

It is trivial to change an abstract type in initialization to a concrete type. It is in general impossible to revert that, as concrete type poisons clients. Why do something irreversible now, when it can easily be done later when it is actually needed?

0

The C++ core guidelines already address this question:

ES.11: Use auto to avoid redundant repetition of type names

The rule foresees clear exceptions, left to the developer's judgement and not written in stone:

Exception: Avoid auto for (...) and in cases where you know exactly which type you want and (...).

You know exactly what you want and why. That's the priority. Go for it. Another way could be to use a factory function create() that returns a pointer to a label interface instead of the blinking label, if the problem is more general.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.