1

I tried the following code from https://en.cppreference.com/w/cpp/thread/get_id in visual studio, and noticed it runs even without #include thread, but not without #include mutex (after commenting out the lines where it is used):

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
 
std::mutex g_display_mutex;
 
void foo()
{
    std::thread::id this_id = std::this_thread::get_id();
 
    g_display_mutex.lock();
    std::cout << "thread " << this_id << " sleeping...\n";
    g_display_mutex.unlock();
 
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
 
int main()
{
    std::thread t1(foo);
    std::thread t2(foo);
 
    t1.join();
    t2.join();
}

I wonder why, and I couldn't find an explanation online. Does mutex include thread? intervention of the IDE? or something else?

ori
  • 43
  • 5
  • I expect the answer is: Because the standard does not say a c++ implementation can not use a header in some other header of the implementation. So mutex is permitted to include thread. – drescherjm Aug 11 '20 at 12:34
  • Are you actually compiling the code and not just looking at the red squiggles (that can be wrong)? – drescherjm Aug 11 '20 at 12:36
  • 2
    Regardless of the explanations why. You should always include those headers that provide the functions, types, … you use in the particular file (or use forward declaration instead of the include if possible). – t.niese Aug 11 '20 at 13:09

1 Answers1

4

One of the headers in your compiler's standard library is internally including a header that defines std::thread (in your case, <mutex> is including this).

The C++ standard only guarantees which definitions will be visible for any #included header -- but it does not control or prevent the implementation from either intentionally or unintentionally making other definitions visible from that header.

Often this happens because the headers in your standard library implementation have been written such that they include other internal headers that bring these definitions transitively (such as <mutex> being written to include <internal/thread.hpp> or something).

Relying on this transitive inclusion is an extremely bad practice, and massively reduces the portability of a given program. This can make it complicated to move between different platforms, standard library implementations, and even different versions of the same compiler if they updated the internal inclusion ordering.


Note: It's important to be aware that just because a standard type may have a dependency to another standard type defined in another header, it does not guarantee that this header will be transitively included by the standard library unless the C++ standard actually dictates this (which is very rarely done).

For example, the exception std::logic_error is defined in <stdexcept>, and derives std::exception defined in <exception>; however #include <stdexcept> is not defined to introduce the symbol std::exception. Some or most standard library implementations may do this; but it is equally legal for a standard library to internally do indirection like the following instead to prevent this from occurring:

<impl/exception.hpp>

namespace std {
  namespace __impl {
     class exception { ... };
  } // namespace __impl 
} // namespace std

<exception>

#include <impl/exception.hpp>

namespace std {
  using __impl::exception;
} // namespace std

<impl/stdexcept.hpp>

#include <impl/exception.hpp>

namespace std {
  namespace __impl {
    class logic_error : public exception { ... }

    /* ... other error types ... */

  } // namespace __impl 
} // namespace std

<stexcept>

#include <impl/stdexcept.hpp>

namespace std {
  // defines 'std::logic_error', and derives 'std::exception', but
  // does not define the symbol 'std::exception' (instead, hides it
  // under std::__impl::exception)
  using __impl::logic_error;

  /* ... other error types ... */

} // namespace std

From the above example you can see that #include <stdexcept> would define the symbol std::logic_error but not std::exception, even though the two types are closely related.

As a result, it's always a good idea to follow include-what-you-use (IWYU) practices wherever possible, even if it appears as though you may be including a redundant header. Otherwise you may be fixing yourself to your current setup, and introducing more complications for future portability (even if it's just for upgrading your compiler)

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59