3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

インクルード順序を変えるとBoostがコンパイルエラーになる

Posted at

Boostを使ってコードを書いているときに遭遇したエラーです。

実行環境

  • Visual Studio 2019 (cl.exe 19.29.30136)
  • boost 1.84.0
  • OpenSSL 3.0.0

再現コード

再現可能なコードは以下の通りです。

main.cpp
#include <boost/asio.hpp>

#include <openssl/ssl.h>

#include <windows.h>
#include <wincrypt.h>

#include <boost/asio/ssl.hpp>
D:\win\boost\boost_1_84_0\boost/asio/ssl/impl/rfc2818_verification.ipp(107): error C2065: 'name': 定義されていない識別子です。
D:\win\boost\boost_1_84_0\boost/asio/ssl/impl/rfc2818_verification.ipp(110): error C2065: 'name': 定義されていない識別子です。
D:\win\boost\boost_1_84_0\boost/asio/ssl/impl/rfc2818_verification.ipp(112): error C2065: 'name': 定義されていない識別子です。

不可解かもしれませんが、一つでもインクルードの順序が変わったり、コメントアウトしたりするとエラーが発生しなくなります。

なぜ発生する?

なんとなくしか原因を把握できていないのですが、最初の#include <boost/asio.hpp>を削除すると、エラーが変化します。

#include <openssl/ssl.h>

#include <windows.h>
#include <wincrypt.h>

#include <boost/asio/ssl.hpp>
D:\win\boost\boost_1_84_0\boost/asio/detail/socket_types.hpp(24): fatal error C1189: #error:  WinSock.h has already been included

これはBoostが吐いたエラーで、推測するにWinSock.hがあらかじめインクルードされている状況を想定していないと思われます。通常はこのチェックにより、問題の所在を明らかにするはずです。

しかし、最初の例ではこのインクルードチェックが行われていません。どういうことかというと、

#include <boost/asio.hpp>
// boost/asio/detail/socket_type.hppをインクルード、WinSock.hをチェック

#include <openssl/ssl.h>
#include <windows.h>
#include <wincrypt.h>
// WinSock.hなどなどをインクルード

#include <boost/asio/ssl.hpp>
// boost/asio/detail/socket_type.hppをインクルードするも、インクルードガードにより無視される
// -> WinSock.hのチェックをすり抜けてエラー

という流れになっていると思われます。

対策

今回はboost関連のインクルードを分散して書いてしまった結果、間に他のヘッダが入り込むことで不可解なエラーが発生してしまいました。Boost関連のヘッダをまとめてインクルードすれば、こういった問題は起こりません。

example1.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <openssl/ssl.h>
#include <windows.h>
#include <wincrypt.h>
example2.cpp
#include <openssl/ssl.h>
#include <windows.h>
#include <wincrypt.h>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
// errorになるが、#error: WinSock.h has already been included となるので解決できる

今回のコードはいささか恣意的にも見えるので、わざわざこんなコードを書こうとは思わないかもしれません。しかし、いろんなライブラリがそれぞれインクルードをした結果、このような構造になってしまうことはしばしばあると思います。

普遍的な対策としては、Boostのような複数のファイルからなるライブラリをインクルードするときに、それ専用のヘッダファイルを作ってしまうということです。

boost_wrapper.hpp
#ifndef MY_BOOST_WRAPPER_HEADER_INCLUDED
#define MY_BOOST_WRAPPER_HEADER_INCLUDED

#include <boost/asio.hpp>
// その他いろいろインクルード

#endif
lib.hpp
#include <boost_wrapper.hpp>
void my_some_func() {}
main.cpp
#include <boost_wrapper.hpp>
#include <lib.hpp>
int main(){}

複数のファイルからBoostなどをインクルードしても、きちんと順序を守ってインクルードされます。が、このような方法がいつもとれるとは限らないし、なにより非効率です。
(#define USE_XXXなどインクルード前に記述が必要な場合などはwrapperを作った方がいいかもしれません)

他の対策としては、別々にコンパイルしてリンクする方法です。今回のケースでは、

  • boost/asio.hppに依存したコード
  • wincrypt.hに依存したコード

の両方をinline展開したくてヘッダに書いてインクルードした結果、衝突が起こってしまいました。この二つを別々のオブジェクトとしてコンパイルした後、必要なインターフェースだけヘッダファイルに摘出してリンクすれば衝突を避けることができます。

所感

とはいえ、c++のプリプロセッサは基本的にグローバルスコープである以上、他のライブラリと何らかの要因で衝突する可能性は排除できません。何らかの実装ミスがない限り、こういったエラーが発生するのはごく稀なのであまり気にしなくてよいと思います。

3
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?