You will want to watch Chandler Carruth’s talk tomorrow at GoingNative. It will be livestreamed here starting at 2:30pm North American Pacific time. (See timeanddate.com for other time zones.)
Watch for the Ghostbusters reference.
That is all.
You will want to watch Chandler Carruth’s talk tomorrow at GoingNative. It will be livestreamed here starting at 2:30pm North American Pacific time. (See timeanddate.com for other time zones.)
Watch for the Ghostbusters reference.
That is all.
Don’t forget that the year’s great C++-fest GoingNative 2013 starts tomorrow morning and will be livestreamed on the Channel 9 home page.
Don’t miss the opening keynote by Bjarne Stroustrup at 9:00am Seattle time on Wednesday (other time zones). It will be followed by many other insightful and enlightening talks, from many of the gurus of C++. The first two days are about Standard C++ on all platforms; the third day contains a mix of portable platform topics and Microsoft-specific content that I think will be of interest to people working on other platforms too.
My talk is first thing Thursday, 9:00am Seattle time:
After a few minutes giving a promised update on VC++ conformance progress, I’ll switch to pure Standard C++ for the rest of the talk and focus on three topics:
I hope that you’ll find it to be informative, and leave with at least one or two thoughts you weren’t expecting to hear about and maybe with a different view of C++ than you had before.
In addition to the livestream, the entire conference will also be available on demand. Note that sessions typically take a few days to post after they occur, so if you miss a talk on the live feed, just be patient and it will appear.
Pre-emptive FAQ: Every time we do this, someone is disappointed that the livestream doesn’t work on their iPads or some other popular hardware. But it actually does work – just look for the alternative-format link if it doesn’t autodetect. You should be able to view it on any major platform.
Enjoy!
Now that the unnecessary headers have been removed, it’s time for Phase 2: How can you limit dependencies on the internals of a class?
1. What does private mean for a class member in C++?
2. Why does changing the private members of a type cause a recompilation?
3. Below is how the header from the previous Item looks after the initial cleanup pass. What further #includes could be removed if we made some suitable changes, and how?
This time, you may make changes to X as long as X‘s base classes and its public interface remain unchanged; any current code that already uses X should not be affected beyond requiring a simple recompilation.
// x.h: sans gratuitous headers
//
#include <iosfwd>
#include <list>
// None of A, B, C, or D are templates.
// Only A and C have virtual functions.
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
class E;
class X : public A, private B {
public:
X( const C& );
B f( int, char* );
C f( int, C );
C& g( B );
E h( E );
virtual std::ostream& print( std::ostream& ) const;
private:
std::list<C> clist;
D d_;
};
std::ostream& operator<<( std::ostream& os, const X& x ) {
return x.print(os);
}
Managing dependencies well is an essential part of writing solid code. C++ supports two powerful methods of abstraction: object-oriented programming and generic programming. Both of these are fundamentally tools to help manage dependencies, and therefore manage complexity. It’s telling that all of the common OO/generic buzzwords—including encapsulation, polymorphism, and type independence—along with most design patterns, are really about describing ways to manage complexity within a software system by managing the code’s interdependencies.
When we talk about dependencies, we usually think of run-time dependencies like class interactions. In this Item, we will focus instead on how to analyze and manage compile-time dependencies. As a first step, try to identify (and root out) unnecessary headers.
1. For a function or a class, what is the difference between a forward declaration and a definition?
2. Many programmers habitually #include many more headers than necessary. Unfortunately, doing so can seriously degrade build times, especially when a popular header file includes too many other headers.
In the following header file, what #include directives could be immediately removed without ill effect? You may not make any changes other than removing or rewriting (including replacing) #include directives. Note that the comments are important.
// x.h: original header
//
#include <iostream>
#include <ostream>
#include <list>
// None of A, B, C, D or E are templates.
// Only A and C have virtual functions.
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
#include "e.h" // class E
class X : public A, private B {
public:
X( const C& );
B f( int, char* );
C f( int, C );
C& g( B );
E h( E );
virtual std::ostream& print( std::ostream& ) const;
private:
std::list<C> clist;
D d_;
};
std::ostream& operator<<( std::ostream& os, const X& x ) {
return x.print(os);
}
A forward declaration of a (possibly templated) function or class simply introduces a name. For example:
class widget; // "widget" names a class
widget* p; // ok: allocates sizeof(*) space typed as widget*
widget w; // error: wait, what? how big is that? does it have a
// default constructor?
Again, a forward declaration only introduces a name. It lets you do things that require only the name, such as declaring a pointer to it—all pointers to objects are the same size and have the same set of operations you can perform on them, and ditto for pointers to nonmember functions, so the name is all you need to make a strongly-typed and fully-usable variable that’s a pointer to class or pointer to function.
What a class forward declaration does not do is tell you anything about what you can do with the type itself, such as what constructors or member functions it has or how big it is if you want to allocate space for one. If you try to create a widget w; with only the above code, you’ll get a compile-time error because widget has no definition yet and so the compiler can’t know how much space to allocate or what functions the type has (including whether it has a default constructor).
A class definition has a body and lets you know the class’s size and know the names and types of its members:
class widget { // "{" means definition
widget();
// ...
};
widget* p; // ok: allocs sizeof(ptr) space typed as widget*
widget w; // ok: allocs sizeof(widget) space typed as widget
// and calls default constructor
Of the first two standard headers mentioned in x.h, one can be immediately removed because it’s not needed at all, and the second can be replaced with a smaller header:
#include <iostream>
Many programmers #include <iostream> purely out of habit as soon as they see anything resembling a stream nearby. Class X does make use of streams, that’s true; but it doesn’t mention anything specifically from iostream, which mainly declares the standard stream objects like cout. At the most, X needs ostream alone for its basic_ostream type, and even that can be whittled down as we will see.
Guideline: Never #include unnecessary header files.
#include <ostream>
Parameter and return types only need to be forward-declared, so instead of the full definition of ostream we really only need its forward declaration.
However, you can’t write the forward declaration yourself using something like class ostream;. First, ostream lives in namespace std in which you can’t redeclare existing standard types and objects. Second, ostream is an alias for basic_ostream<char> which you couldn’t reliably forward-declare even if you were allowed to because library implementations are allowed to do things like add their own extra template parameters beyond those required by the standard that of course your code wouldn’t know about—which is one of the primary reasons for the rule that programmers aren’t allowed to write their own declarations for things in namespace std.
All is not lost, though: The standard library helpfully provides the header iosfwd, which contains forward declarations for all of the stream templates and their standard aliases, including basic_ostream and ostream. So all we need to do is replace #include <ostream> with #include <iosfwd>.
Guideline: Prefer to #include <iosfwd> when a forward declaration of a stream will suffice.
Incidentally, once you see iosfwd, one might think that the same trick would work for other standard library templates like string and list. There are, however, no comparable “stringfwd” or “listfwd” standard headers. The iosfwd header was created to give streams special treatment for backwards compatibility, to avoid breaking code written in years past for the “old” non-templated version of the iostreams subsystem. It is hoped that a real solution will come in a future version of C++ that supports modules, but that’s a topic for a later time.
There, that was easy. We can now move on to…
… what? “Not so fast!” I hear some of you say. “This header does a lot more with ostream than just mention it as a parameter or return type. The inlined operator<< actually uses an ostream object! So it must need ostream‘s definition, right?”
That’s a reasonable question. Happily, the answer is: No, it doesn’t. Consider again the function in question:
std::ostream& operator<<( std::ostream& os, const X& x ) {
return x.print(os);
}
This function mentions an ostream& as both a parameter and a return type, which most people know doesn’t require a definition. And it passes its ostream& parameter in turn as a parameter to another function, which many people don’t know doesn’t require a definition either—it’s the same as if it were a pointer, ostream*, discussed above. As long as that’s all we’re doing with the ostream&, there’s no need for a full ostream definition—we’re not really using an ostream itself at all, such as by calling functions on it, we’re only using a reference to type for which we only need to know the name. Of course, we would need the full definition if we tried to call any member functions, for example, but we’re not doing anything like that here.
So, as I was saying, we can now move on to get rid of one of the other headers, but only one just yet:
#include "e.h" // class E
Class E is just being mentioned as a parameter and as a return type in function E h(E), so no definition is required and x.h shouldn’t be pulling in e.h in the first place because the caller couldn’t even be calling this function if he didn’t have the definition of E already, so there’s no point in including it again. (Note this would not be true if E were only a return type, such as if the signature were E h();, because in that case it’s good style to include E’s definition for the caller’s convenience so he can easily write code like auto val = x.h();.) All we need to do is replace #include “e.h” with class E;.
Guideline: Never #include a header when a forward declaration will suffice.
That’s it.
You may be wondering why we can’t get rid of the other headers yet. It’s because to define class X means you need to know its size in order to know how much space to allocate for an X object, and to know X’s size you need to know at least the size of every base class and data member. So we need the definitions of A and B because they are base classes, and we need the header definitions of list, C, and D because they are used to define the data members. How we can begin to address some of these is the subject of Part 2…
Thanks to the following for their feedback to improve this article: Gennaro, Sebastien Redl, Emmanuel Thivierge.
In recent months, more comment spam has been getting through. To deal with it, I’ve had to tighten up and hold more comments for moderation, which means some comments may be delayed in appearing until I manually approve them. Also, I’ve noticed that WordPress seems to have similarly tightened their settings for auto-identifying spam that doesn’t even reach the moderation queue, and so those comments will be similarly delayed until I check the Spam folder and manually mark them as not-spam.
Unfortunately, this means your comment may be delayed in appearing, usually by a few hours but sometimes up to one business day (I have a day job). My policy is to approve all comments whether they agree with me or not, except only for spam and obscene or content-free flames, so if you leave a legitimate comment but don’t see it appear within a day or so, feel free to email me.
GotW #7a: Minimizing Compile-Time Dependencies, Part 1
Managing dependencies well is an essential part of writing solid code. C++ supports two powerful methods of abstraction: object-oriented programming and generic programming. Both of these are fundamentally tools to help manage dependencies, and therefore manage complexity. It’s telling that all of the common OO/generic buzzwords—including encapsulation, polymorphism, and type independence—along with the lion’s share of design patterns, are really about describing ways to manage complexity within a software system by managing the code’s interdependencies.
When we talk about dependencies, we usually think of run-time dependencies like class interactions. In this Item, we will focus instead on how to analyze and manage compile-time dependencies. As a first step, try to identify (and root out) unnecessary headers.
1. For a function or a class, what is the difference between a forward declaration and a definition?
2. Many programmers habitually #include many more headers than necessary. Unfortunately, doing so can seriously degrade build times, especially when a popular header file includes too many other headers.
In the following header file, what #include directives could be immediately removed without ill effect? You may not make any changes other than removing or rewriting #include directives. Note that the comments are important.
// x.h: original header
//
#include <iostream>
#include <ostream>
#include <list>
// None of A, B, C, D or E are templates.
// Only A and C have virtual functions.
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
#include "e.h" // class E
class X : public A, private B {
public:
X( const C& );
B f( int, char* );
C f( int, C );
C& g( B );
E h( E );
virtual std::ostream& print( std::ostream& ) const;
private:
std::list<C> clist;
D d_;
};
std::ostream& operator<<( std::ostream& os, const X& x ) {
return x.print(os);
}
Toward correct-by-default, efficient-by-default, and pitfall-free-by-default variable declarations, using “AAA style”… where “triple-A” is both a mnemonic and an evaluation of its value.
1. What does this code do? What would be a good name for some_function?
template<class Container, class Value>
void some_function( Container& c, const Value& v ) {
if( find(begin(c), end(c), v) == end(c) )
c.emplace_back(v);
assert( !c.empty() );
}
2. What does “write code against interfaces, not implementations” mean, and why is it generally beneficial?
3. What are some popular concerns about using auto to declare variables? Are they valid? Discuss.
4. When declaring a new local variable x, what advantages are there to declaring it using auto and one of the two following syntaxes:
(a) auto x = init; when you don’t need to commit to a specific type? (Note: The expression init might include calling a helper that performs partial type adjustment, such as as_signed, while still not committing to a specific type.)
(b) auto x = type{ init }; when you do want to commit to a specific type by naming a type?
List as many as you can. (Hint: Look back to GotW #93.)
5. Explain how using the style suggested in #4 is consistent with, or actively leverages, the following other C++ features:
(a) Heap allocation syntax.
(b) Literal suffixes, including user-defined literal operators.
(c) Named lambda syntax.
(d) Function declarations.
(e) Template alias declarations.
6. Are there any cases where it is not possible to use the style in #4 to declare all local variables?
template<class Container, class Value>
void append_unique( Container& c, const Value& v ) {
if( find(begin(c), end(c), v) == end(c) )
c.emplace_back(v);
assert( !c.empty() );
}
Let’s call this function append_unique. First, it checks to see whether the value v is already in the container. If not, it appends it at the end. Finally, it asserts that c is not empty, since by now it must contain one copy of the value v.
You probably thought this question was fairly easy.
Maybe too easy.
If so, good. That’s the point of the example. Hold the thought, and we’ll come back to this in Question 3.
It means we should care principally about “what,” not “how.” This separation of concerns applies at all levels in high-quality modern software—hiding code, hiding data, and hiding type. Each increases encapsulation and reduces coupling, which are essential for large-scale and robust software.
Please indulge a little repetition in the following paragraphs. It’s there to make a point about similarity.
Hiding code. With the invention of separately compiled functions and structured programming, we gained “encapsulation to hide code.” The caller knows the signature only—the function’s internal code is not his concern and not accessible programmatically, even if the function is inline and the body happens to be visible in source code. We try hard not to inadvertently leak implementation details, such as internal data structure types. The point is that the caller does not, and should not, commit to knowledge of the current internal code; if he did, it would create interdependencies and make separately compiled libraries impossible.
Hiding data (and code). With object oriented styles (OO), we gained two new manifestations of this separation. First, we got “more encapsulation to hide both code and data.” The caller knows the class name, bases, and member function signatures only—the class’s internal data and internal code are hidden and not accessible programmatically, even though the private class members are lexically visible in the class definition and inline function bodies may also be visible. (In turn, dynamic libraries and the potential future-C++ modules work aim to accomplish the same thing at a still larger scale.) Again we try hard not to inadvertently leak implementation details, and again the point is that the caller does not, and should not, commit to knowledge of the current internal data or code, which would make the class difficult to ever change or to ship on its own as a library.
Hiding type (run-time polymorphism). Second, OO also gave us “separation of interfaces to hide type.” A base class or interface can delegate work to a concrete derived implementation via virtual functions. Now the interface the caller sees and the implementation are actually different types, and the caller knows the base type only—he doesn’t know or care about the concrete type, including even its size. The point, once again, is that the caller does not, and should not, commit to a single concrete type, which would make the caller’s code less general and less able to be reused with new types.
Hiding type (compile-time polymorphism). With templates, we gained a new compile-time form of this separation—and it’s still “separation of interfaces to hide type.” The caller knows an ad-hoc “duck typed” set of operations he wants to perform using a type, and any type that supports those operations will do just fine. The contemplated future C++ concepts feature will allow making this stricter and less ad-hoc, but still avoids committing to a concrete type at all. The whole point is still is that the caller does not, and should not, commit to a single concrete type, which would make the caller’s code less generic and less able to be reused with new types.
In many languages, not just C++, there are several reasons people commonly give for why they are reluctant to use auto to declare variables (or the equivalent in another language, such as var or let). We could summarize them as: laziness, commitment, and readability. Let’s take them in order.
First, laziness: One common concern is that “writing auto to declare a variable is primarily about saving typing.” However, this is just a misunderstanding of auto. As we saw in GotW #92 and #93 and will see again below, the main reasons to declare variables using auto are for correctness, performance, maintainability, and robustness—and, yes, convenience, but that’s in last place on the list.
Guideline: Remember that preferring auto variables is motivated primarily by correctness, performance, maintainability, and robustness—and only lastly about typing convenience.
Second, commitment: “But in some cases I do want to commit to a specific type, not automatically deduce it, so I can’t use auto.” It’s true that sometimes you do want to commit to a specific type, but you can still use auto. As demonstrated in GotW #92 and #93, not only can you still write declarations of the form auto x = type{ init }; (instead of type x{init};) to commit to a specific type, but there are good reasons for doing so, such as that saying auto means you can’t possibly forget to initialize the variable.
Guideline: Consider declaring local variables auto x = type{ expr }; when you do want to explicitly commit to a type. It is self-documenting to show that the code is explicitly requesting a conversion, it guarantees the variable will be initialized, and it won’t allow an accidental implicit narrowing conversion. Only when you do want explicit narrowing, use ( ) instead of { }.
The third and most common argument concerns readability: “My code gets unreadable quickly when I don’t know what exact type my variable is without hunting around to see what that function or expression returns, so I can’t just use auto all the time.” There is truth to this, including losing the ability to search for occurrences of specific types when using the non-typed syntax auto x = expr; in 4(a) below, so this appears at first to be a strong argument. And it’s true that any feature can be overused. However, I think this argument is actually weaker than it first seems for four reasons, two minor and two major.
The two minor counterarguments are:
But we should focus on the two major counterarguments:
“… Wait, what? I do not ignore types all the time,” someone might say. Actually, not only do you do it, but you’re so comfortable and cavalier about it that you may not even realize you’re doing it. Let’s go back to that code in Question 1:
template<class Container, class Value>
void append_unique( Container& c, const Value& v ) {
if( find(begin(c), end(c), v) == end(c) )
c.emplace_back(v);
assert( !c.empty() );
}
Quick quiz: How many specific types are mentioned in that function? Name as many as you can.
Take a moment to consider that before reading on…
… We can see pretty quickly that the answer is a nice round number: Zero. Zilch. (Pedantic mode: Yes, there’s void, but I’m going to declare that void doesn’t count because it’s to denote “no type,” it’s not a meaningful type.)
Not a single specific type appears anywhere in this code, and the lack of exact types makes it much more powerful and doesn’t significantly harm its readability. Like most people, you probably thought Question 1 felt “easy” when we did it in isolation. Granted, this is generic code, and not all your code will be templates—but the point is that the code isn’t unreadable even though it doesn’t mention specific types, and in fact auto gives you the ability to write generic code even when not writing a template.
So starting with the cases illustrated in this short example, let’s consider some places where we routinely ignore exact types. First, function template parameters:
Second, function return values:
Third, many function parameters:
And that’s just in this example. We routinely and desirably ignore types in many other places, such as:
There are probably more.
Although lack of commitment may be a bad thing in other areas of life, not committing to a specific type is often desirable by default in reusable code.
Let’s consider the base case first, which has by far the strongest arguments in its favor and is gaining quite a bit of traction in the C++ community.
GotW #93 offered many concrete examples to support habitually declaring local variables using auto x = expr; when you don’t need to explicitly commit to a type. The advantages include:
See GotW #93 for concrete examples of these cases, where using auto helps eliminate correctness bugs, performance bugs, and silently nonportable code.
As noted in the questions, the expression init might include calling a helper that performs partial type adjustment, such as as_signed, while still not committing to a specific type. As shown in GotW #93, prefer to use auto x = as_signed(integer_expr); or auto x = as_unsigned(integer_expr); to store the result of an integer computation that should be signed or unsigned—these should be viewed as “casts that preserve width,” so we are not casting to a specific type but rather casting an attribute of the type while correctly preserving the other basic characteristics of the type, notably by not forcing it to commit to a particular size.
Using auto together with as_signed or as_unsigned makes code more portable: the variable will both be large enough (thanks to auto) and preserve the required signedness on all platforms. Note that signed/unsigned conversions within integer_expr may still occur and so you may need additional finer-grained as_signed/as_unsigned casts within the expression for full portability.
This is the explicitly typed form, and it still has advantages but they are not as clearly strong as implicitly typed form. The jury is still out on whether to recommend this one wholesale, as we’re still trying it out, but it does offer some advantages and I suggest you try it out for a while and see if it works well for you.
So here’s the recommendation to consider trying out for yourself: Consider declaring local variables auto x = type{ expr }; when you do want to explicitly commit to a type. (Only when you do want to allow explicit narrowing, use ( ) instead of { }.) The advantages of this typed auto declaration style include:
… which brings us to Question 5.
Let’s start off this question with some side-by-side examples that give us a taste of the symmetry we gain when we habitually declare variables using modern auto style. Starting with two examples where we don’t need to commit to a type and then two where we do, we see that the right-hand style is not only more robust and maintainable for the reasons already given, but also arguably cleaner and more regular with the type consistently on the right when it is mentioned:
// Classic C++ declaration order // Modern C++ style
const char* s = "Hello"; auto s = "Hello";
widget w = get_widget(); auto w = get_widget();
employee e{ empid }; auto e = employee{ empid };
widget w{ 12, 34 }; auto w = widget{ 12, 34 };
Now consider the (dare we say elegant) symmetry with each of the following.
When allocating heap variables, did you notice that the type name is already on the right naturally anyway? And since it’s there, we don’t want to have to repeat it. (I’ll show the raw “new” form for completeness, but prefer make_unique and make_shared in that order for allocation in modern code, resorting to raw new only well-encapsulated inside the implementation of low-level data structures.)
// Classic C++ declaration order // Modern C++ style
widget* w = new widget{}; /* auto w = new widget{}; */
unique_ptr<widget> w auto w = make_unique<widget>();
= make_unique<widget>();
Using auto declaration style doesn’t merely work naturally with built-in literal suffixes like ul for unsigned long, plus user-defined literals including standard ones now in draft C++14, but it actively encourages using them:
// Classic C++ declaration order // Modern C++ style
int x = 42; auto x = 42;
float x = 42.; auto x = 42.f;
unsigned long x = 42; auto x = 42ul;
std::string x = "42"; auto x = "42"s; // C++14
chrono::nanoseconds x{ 42 }; auto x = 42ns; // C++14
Based on the examples so far, which do you think is more regular? But wait, there’s more…
Lambdas have unutterable types, and auto is the best way to capture them exactly and efficiently. But because their declarations are now so similar, let’s consider lambdas and (other) functions together, and in the last two lines of this example also use C++14 return type deduction:
// Classic C++ declaration order // Modern C++ style
int f( double ); auto f (double) -> int;
… auto f (double) { /*...*/ };
… auto f = [=](double) { /*...*/ };
Modern C++ frees us from the tyranny of un-template-able typedef:
// Classic C++ workaround // Modern C++ style
typedef set<string> dict; using dict = set<string>;
template<class T> struct myvec { template<class T>
typedef vector<T,myalloc> type; using myvec = vector<T,myalloc>;
};
Have you noticed that the C++ world is moving to a left-to-right declaration style everywhere, of the form
category name = type and/or initializer ;
where “category” can be auto or using?
Take a moment to re-skim the two columns of examples above. Even ignoring correctness and performance advantages, do you find the right-hand column to be most consistent, and most readable?
There is one case I know of where this style cannot be followed, and it applies to the type-specific auto x = type{ init }; form. In that form, type has to be moveable (even though the move operation will be routinely elided by compilers), so these won’t work:
auto lock = lock_guard<mutex>{ m }; // error, not moveable
auto ai = atomic<int>{}; // error, not moveable
(Aside: For at least some of these cases, an argument could be made that this is actually more of a defect in the type itself, in particular that perhaps atomic<int> should be moveable.)
Having said that, there are three other cases I know of that you might encounter that may at first look like they don’t work with this auto style, but actually do. Let’s consider those for completeness.
First, the basic form auto x = init; will exactly capture an initializer_list or a proxy type, such as an expression template. This is a feature, not a bug, because you have a convenient way to spell both “capture the list or proxy” and “resolve the computation” depending which you mean, and the default syntax goes to the more efficient one: If you want to efficiently capture the list or proxy, use the basic form which gives you performance by default, and if you mean to force the proxy to resolve the computation, specify the explicit type to ask for the conversion you want. For example:
auto i1 = { 1 }; // initializer_list<int>
auto i2 = 1; // int
auto a = matrix{...}, b = matrix{...}; // some type that does lazy eval
auto ab = a * b; // to capture the lazy-eval proxy
auto c = matrix{ a * b }; // to force computation
Second, here is a rare case that you may discover now that we have auto: Due to the mechanics of the C++ grammar, you can’t legally write a multi-word type like long long or class widget in the place where type goes in the auto x = type{ init }; form. However, note that this affects only those two cases:
So just avoid the multi-word form and use the better alternative instead:
auto x = long long{ 42 }; // error
auto x = int64_t{ 42 }; // ok, better
auto x = 42LL; // ok, better
auto y = class X{1,2,3}; // error
auto y = X{1,2,3}; // ok
We already ignore explicit and exact types much of the time, including with temporary objects, virtual functions, templates, and more. This is a feature, not a bug, because it makes our code less tightly coupled, and more generic, flexible, reusable, and future-proof.
Declaring variables using auto, whether or not we want to commit to a type, offers advantages for correctness, performance, maintainability, and robustness, as well as typing convenience. Furthermore, it is an example of how the C++ world is moving to a left-to-right declaration style everywhere, of the form
category name = type and/or initializer ;
where “category” can be auto or using, and we can get not only correctness and performance but also consistency benefits by using the style to consistently declare local variables (including using literals and user-defined literals), function declarations, named lambdas, aliases, template aliases, and more.
Thanks in particular to Scott Meyers and Andrei Alexandrescu for their time and insights in reviewing and discussing drafts of this material. Both helped generate candidate names for this idiom; it was Alexandrescu who suggested the name “AAA (almost always auto)” which I merged with the best names I’d thought of to that point (“auto style” or “auto (+type) style”) to get “AAA Style (almost always auto).” Thanks also to the following for their feedback to improve this article: Adrian, avjewe, mttpd, ned, zadecn, noniussenior, Marcel Wid, J Guy Davidson, Mark Garcia, Jonathan Wakely, “x y.”
I don’t often link to other articles, but this one is worth reading.
by Drew Crawford
… So if you are trying to figure out exactly what brand of crazy all your native developer friends are on for continuing to write the evil native applications on the cusp of the open web revolution, or whatever, then bookmark this page, make yourself a cup of coffee, clear an afternoon, find a comfy chair, and then we’ll both be ready.
He offers data (imagine!) to justly debunk many common memes and “easy answers” that routinely litter HN/Reddit/Slashdot comment threads. The piece is also often subtly (and intentionally) hilarious – do watch for the subtle humor, not just the obvious wit.
Don’t be distracted by the author’s viewpoint and emphasis on “iOS and Javascript” development – the article covers lots of important ground, including:
I agree with most of it, and not just because he quotes from my When Will Better JITs Save Managed Code blog post from last year.
Recommended.
A few takeaways from the conclusion (spoiler alert):
Garbage collection is exponentially bad in a memory-constrained environment. It is way, way worse than it is in desktop-class or server-class environments.
Every competent mobile developer, whether they use a GCed environment or not, spends a great deal of time thinking about the memory performance of the target device
JavaScript, as it currently exists, is fundamentally opposed to even allowing developers to think about the memory performance of the target device
If they did change their minds and allowed developers to think about memory, experience suggests this is a technically hard problem.
asm.js show some promise, but even if they win you will be using C/C++ or similar “backwards” language as a frontend, rather than something dynamic like JavaScript
The session schedule for this week’s //build/ conference in San Francisco has now been posted.
I have a talk on Friday at noon Pacific time, titled “The Future of C++.” Note this is a Microsoft conference, so the talk is specifically about the future of the Visual C++ product, but nevertheless it’s all about Standard C++ because I’ll start with a short update on ISO goings-on and the bulk of the talk will be an update on standards conformance in Visual C++ and explaining a number of the most modern ISO C++ features.
On Friday, you can watch my talk live at Channel 9. In the meantime, you can get the keynotes and some other major sessions at the same link all day today, tomorrow, and Friday… as I write this, a cool guy named Steve has the camera and just gave away thousands of nifty 8″ tablets. I’ll be in the same place in two days to talk C++.
If you’re in San Francisco for //build/ and care about C++, you want to be in South Hall: Gateway Ballroom for session 2-306.
If you’re not at the conference but use Visual C++ as one of your C++ compilers, you’ll want to watch the talk live in the webcast or on demand about 24-48 hours after the talk ends.
Even if you don’t use Visual C++ a lot right now, you might find some of the ISO C++ standards context and updates interesting.
Stay tuned.
Toward correct-by-default, efficient-by-default, and pitfall-free-by-default variable declarations, using “AAA style”… where “triple-A” is both a mnemonic and an evaluation of its value.
1. What does this code do? What would be a good name for some_function?
template<class Container, class Value>
void some_function( Container& c, const Value& v ) {
if( find(begin(c), end(c), v) == end(c) )
c.emplace_back(v);
assert( !c.empty() );
}
2. What does “write code against interfaces, not implementations” mean, and why is it generally beneficial?
3. What are some popular concerns about using auto to declare variables? Are they valid? Discuss.
4. When declaring a new local variable x, what advantages are there to declaring it using auto and one of the two following syntaxes:
(a) auto x = init; when you don’t need to commit to a specific type? (Note: The expression init might include calling a helper that performs partial type adjustment, such as as_signed, while still not committing to a specific type.)
(b) auto x = type{ init }; when you do want to commit to a specific type by naming a type?
List as many as you can. (Hint: Look back to GotW #93.)
5. Explain how using the style suggested in #4 is consistent with, or actively leverages, the following other C++ features:
(a) Heap allocation syntax.
(b) Literal suffixes, including user-defined literal operators.
(c) Named lambda syntax.
(d) Function declarations.
(e) Template alias declarations.
6. Are there any cases where it is not possible to use the style in #4 to declare all local variables?