Show Lecture.ImplicitInclusion as a slide show.
CS253 Implicit Inclusion
Dubious Code
Is this code correct?
#include <iostream>
using namespace std;
int main() {
string s = "Han shot first!\n";
cout << s;
}
Han shot first!
Bad Code
#include <iostream>
using namespace std;
int main() {
string s = "Han shot first!\n";
cout << s;
}
Han shot first!
The code is not correct. A std::string
was defined,
but there is no #include <string>
. But, still, it compiled. ☺? ☹?
Not a Cause for Celebration
- You may think, “What’s the problem? So it compiles.
That doesn’t bother me. Now, if it ever fails to compile,
let me know.”
- Sure, it compiles, but it doesn’t have to.
- It might stop compiling come the next compiler release.
- It might not compile using another compiler (e.g., clang++ vs. g++).
operator<<
- Consider the statement
cout << s;
. This meaning of <<
isn’t
built into C++ like integer addition. Instead, somebody must have
written a operator<<
function that takes an ostream
and a
std::string
.
- Where is that operator declared?
- The obvious places are
<iostream>
or <string>
.
- Wherever it is, it has to know about both classes:
ostream
and string
.
- If it’s in
<iostream>
, then <iostream>
has to #include <string>
.
- If it’s in
<string>
, then <string>
has to #include <iostream>
.
Light Dawns
#include <iostream>
using namespace std;
int main() {
string s = "Han shot first!\n";
cout << s;
}
Han shot first!
- Now, we understand how this can compile.
- It must be that, on this particular compiler,
<iostream>
includes <string>
.
- Can we count on that? No!
- On another computer,
<string>
might include <iostream>
.
Include Guards
Remember include guards? They work like this:
<Foo.h> contains:
#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
… define the class Foo here …
#endif /* FOO_H_INCLUDED */
We don’t usually bother with the indentation.
This Could Happen
One could construct <iostream>
& <string>
so they don’t
#include
each other:
- Remember compile-time symbols such as
IOSTREAM_INCLUDED
,
used for #include
guards?
- They were used to see if this header file has already been
#include
d.
- Those symbols are visible outside of the files they’re defined in.
- Let’s use those symbols to detect if a different header file
has been
#include
d.
- If both
<iostream>
and <string>
get included,
then define operator<<
.
- Otherwise, don’t define
<<
, because they won’t need it.
Like this:
<iostream>:
#ifndef IOSTREAM_INCLUDED
#define IOSTREAM_INCLUDED
#ifdef STRING_INCLUDED
#include <string-output-operator>
#endif
#endif /* IOSTREAM_INCLUDED */
<string>:
#ifndef STRING_INCLUDED
#define STRING_INCLUDED
#ifdef IOSTREAM_INCLUDED
#include <string-output-operator>
#endif
#endif /* STRING_INCLUDED */
<string-output-operator>:
std::ostream &operator<<(std::ostream &, const std::string &);
Solution
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "Han shot first!\n";
cout << s;
}
Han shot first!
- To write a portable program:
#include
what you need.
- Don’t count on an accident of implementation.
- It’s a shame that the compiler doesn’t tell us that we got it wrong.
- This is why computer programmers get the big bucks.
Theoretical
- Is this just theoretical handwringing? No.
- Compiler vendors are currently quite casual about having
one header file include another.
- It is permitted by the standard.
- For nearly every assignment, somebody turns in code
that compiles on their computer, but fails on CSU computers.
Example
Consider this program, compiled on my Macbook Air under macOS:
% cat c.cc
#include <cmath>
int main() {
return isinf(0.0);
}
% g++ c.cc
% ./a.out
% g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin15.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Example
The same program, compiled on a CSU CS Department computer:
% cat c.cc
#include <cmath>
int main() {
return isinf(0.0);
}
% g++ c.cc
c.cc: In function ‘int main()’:
c.cc:3:12: error: ‘isinf’ was not declared in this scope
return isinf(0.0);
^~~~~
c.cc:3:12: note: suggested alternative:
In file included from c.cc:1:0:
/usr/include/c++/7/cmath:612:5: note: ‘std::isinf’
isinf(_Tp __x)
^~~~~
% g++ --version
g++ (GCC) 7.3.1 20180130 (Red Hat 7.3.1-2)
Conclusion
TEST ON THE TARGET MACHINE