C++ / CMake / Conan.io - 'Undefined Reference to Class::Method' 当包含 header
C++ / CMake / Conan.io - 'Undefined Reference to Class::Method' when including header
编译项目时遇到了一些麻烦
我正在构建一个小型 OpenGL-based project. I use an MVC 架构。我希望 CMake 产生两个不同的 .exe
:
main.exe
是一个小型的 GLFW window,具有简单的 OpenGL 上下文。它构建和工作完全好,使用conan.io来管理使用的库。
pentest.exe
是一个简单的测试可执行文件,我想用它来测试模型中的一些基本功能。当我调用 make
命令时,这个 不会被编译 。
这里是我简化的项目架构:
├───build
│ ├───.cmake
│ │
│ ├───bin
│ │ └─── // .exe files are here
│ │
│ ├───CMakeFiles
│ │ └─── // Cmake files are here
│ │
│ └─── // ...
│
├───include
│ ├───GL
│ │ └─── GLU.h
│ │
│ └───model
│ ├───Block.hpp
│ └───GameGrid.hpp
│
├───src
│ ├───model
│ │ ├───Block.hpp
│ │ └───GameGrid.hpp
│ │
│ ├───main.cpp
│ └───pentest.cpp
│
├───CMakeLists.txt
└───conanfile.txt
请注意:
pentest.cpp
不依赖任何外部库。
- 即使我的
GameGrid
class 是一个模板 class,我还是让 header 在末尾包含了实现文件(在 this Whosebug question 之后) .
- 我对 CMake 非常非常糟糕。
- CMake 命令运行良好,当
make
命令为 pentest.exe
. 调用 linker 时出现错误
这是我的 CMakeLists.txt
:
cmake_minimum_required(VERSION 2.8.12)
project(TheEndless)
add_definitions("-std=c++17")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/model
)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(main src/main.cpp)
add_executable(pentest src/pentest.cpp)
target_link_libraries(main ${CONAN_LIBS})
target_link_libraries(pentest ${CONAN_LIBS})
这是我的 pentest.cpp
:
#include <iostream>
#include <string>
#include "model/Block.hpp"
#include "model/GameGrid.hpp"
int main(int argc, char const *argv[]) {
theendless::model::Block b;
theendless::model::GameGrid<1, 1> g;
g(0, 0) = b;
std::string s(g(0, 0).get_name());
std::cout << s << std::endl;
return 0;
}
这是我的 model/Block.hpp
:
#ifndef THEENDLESS_MODEL_BLOCK_HPP
#define THEENDLESS_MODEL_BLOCK_HPP
#include <string>
namespace theendless::model {
class Block {
private:
std::string name;
public:
Block();
Block(std::string name);
std::string get_name() const;
void set_name(const std::string newName);
};
}
#endif
这是我的model/Block.cpp
:
#include "model/Block.hpp"
#include <string>
namespace theendless::model {
Block::Block() : name("default_name") {}
Block::Block(std::string name) : name(name) {}
std::string Block::get_name() const { return this->name; }
void Block::set_name(const std::string newName) { this->name = newName; }
}
这是 make
显示的错误:
PS C:\projects\TheEndless\build> make
Scanning dependencies of target pentest
[ 75%] Building CXX object CMakeFiles/pentest.dir/src/pentest.cpp.obj
[100%] Linking CXX executable bin/pentest.exe
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/pentest.dir/objects.a(pentest.cpp.obj): in function `main':
C:/projects/TheEndless/src/pentest.cpp:9: undefined reference to `theendless::model::Block::Block()'
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/projects/TheEndless/src/pentest.cpp:13: undefined reference to `theendless::model::Block::get_name[abi:cxx1c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/pentest.dir/objects.a(pentest.cpp.obj): in function `std::array<theendless::model::Block, 1ull>::array()':
c:/mingw/include/c++/9.2.0/array:94: undefined reference to `theendless::model::Block::Block()'
collect2.exe: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/pentest.dir/build.make:107: bin/pentest.exe] Error 1
make[1]: *** [CMakeFiles/Makefile2:124: CMakeFiles/pentest.dir/all] Error 2
make: *** [Makefile:103: all] Error 2
请注意,将 model/Block.cpp
包含到 pentest.cpp
会使问题消失,但我有点想让项目干净,所以我不想这样做。
附加信息:
- 我在 windows 10.
- 我正在使用 VSCode 编辑我的文件,我通过在集成的 *PowerShell 终端中键入
make
进行编译。
- 我正在使用 Mingw distro 进行编译。
如有任何帮助,我们将不胜感激! :)
我不是 CMake 或编译器专家,但我是这样理解发生了什么的。
编译器不会四处搜索任何头文件或源文件,除非它被告知必须这样做。但是编译器什么时候必须搜索它们?
- 文件(通常是文件头)包含在编译器已知的文件中。
- 该文件已明确为编译器所知(例如,在 CMakeLists.txt 中)。
在你的例子中,编译器知道头文件,因为它在 pentest.cpp 源文件中 #include
d(上面的变体 1)。编译器是怎么知道 pentest.cpp 的?在函数 add_executable
中明确指出特定构建目标 pentest
是从该文件构建的。
现在Block.cpp呢?编译器不知道,因为 CMakeLists.txt 中既没有包含也没有说明编译器必须使用此文件。所以编译器无法知道它。
正如您已经提到的,包含 .cpp 文件不是一个好的样式。仅包含头文件的一个巨大优势(在我看来)是,如果函数的实现发生变化(不是它的声明,而是函数的主体),您不必重新编译使用它的所有内容。您只需重新编译那个 .cpp 文件。
那么解决方法是什么?您必须让编译器知道应该用于构建目标的所有源文件 pentest
。因此,您必须将这些源文件添加到函数 add_executable
中。 CMake docs for add_executable告诉你以下内容。
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
Adds an executable target called to be built from the source files listed in the command invocation.
因此您必须将所有用于构建目标的源文件添加到相同的 add_executable
命令调用中。在您的情况下,CMakeLists.txt 看起来像这样。请注意,我必须在我的机器上添加 target_compile_features
并删除 add_compile_definitions
以强制使用 C++17。
cmake_minimum_required(VERSION 2.8.12)
project(TheEndless)
# add_definitions("-std=c++17") # does not work on my Windows 10 machine
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/model
)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(main src/main.cpp)
# add all source files needed to build to the executable
# GameGrid.cpp is not needed because it is included in the header.
add_executable(pentest src/pentest.cpp src/model/Block.cpp)
target_link_libraries(main ${CONAN_LIBS})
target_link_libraries(pentest ${CONAN_LIBS})
target_compile_features(pentest PRIVATE cxx_std_17) # added to really use C++17, see the docs for explanation
我只看到“问题”:Visual Studio 将 Block.hpp、GameGrid.hpp 和 GameGrid.cpp 标记为“外部依赖项”。如果您希望它们显示为目标的一部分,您也可以将它们添加到 add_executable
。我不确定是否有其他解决方案,因为它似乎有点多余。
我正在构建一个小型 OpenGL-based project. I use an MVC 架构。我希望 CMake 产生两个不同的 .exe
:
main.exe
是一个小型的 GLFW window,具有简单的 OpenGL 上下文。它构建和工作完全好,使用conan.io来管理使用的库。pentest.exe
是一个简单的测试可执行文件,我想用它来测试模型中的一些基本功能。当我调用make
命令时,这个 不会被编译 。
这里是我简化的项目架构:
├───build
│ ├───.cmake
│ │
│ ├───bin
│ │ └─── // .exe files are here
│ │
│ ├───CMakeFiles
│ │ └─── // Cmake files are here
│ │
│ └─── // ...
│
├───include
│ ├───GL
│ │ └─── GLU.h
│ │
│ └───model
│ ├───Block.hpp
│ └───GameGrid.hpp
│
├───src
│ ├───model
│ │ ├───Block.hpp
│ │ └───GameGrid.hpp
│ │
│ ├───main.cpp
│ └───pentest.cpp
│
├───CMakeLists.txt
└───conanfile.txt
请注意:
pentest.cpp
不依赖任何外部库。- 即使我的
GameGrid
class 是一个模板 class,我还是让 header 在末尾包含了实现文件(在 this Whosebug question 之后) . - 我对 CMake 非常非常糟糕。
- CMake 命令运行良好,当
make
命令为pentest.exe
. 调用 linker 时出现错误
这是我的 CMakeLists.txt
:
cmake_minimum_required(VERSION 2.8.12)
project(TheEndless)
add_definitions("-std=c++17")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/model
)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(main src/main.cpp)
add_executable(pentest src/pentest.cpp)
target_link_libraries(main ${CONAN_LIBS})
target_link_libraries(pentest ${CONAN_LIBS})
这是我的 pentest.cpp
:
#include <iostream>
#include <string>
#include "model/Block.hpp"
#include "model/GameGrid.hpp"
int main(int argc, char const *argv[]) {
theendless::model::Block b;
theendless::model::GameGrid<1, 1> g;
g(0, 0) = b;
std::string s(g(0, 0).get_name());
std::cout << s << std::endl;
return 0;
}
这是我的 model/Block.hpp
:
#ifndef THEENDLESS_MODEL_BLOCK_HPP
#define THEENDLESS_MODEL_BLOCK_HPP
#include <string>
namespace theendless::model {
class Block {
private:
std::string name;
public:
Block();
Block(std::string name);
std::string get_name() const;
void set_name(const std::string newName);
};
}
#endif
这是我的model/Block.cpp
:
#include "model/Block.hpp"
#include <string>
namespace theendless::model {
Block::Block() : name("default_name") {}
Block::Block(std::string name) : name(name) {}
std::string Block::get_name() const { return this->name; }
void Block::set_name(const std::string newName) { this->name = newName; }
}
这是 make
显示的错误:
PS C:\projects\TheEndless\build> make
Scanning dependencies of target pentest
[ 75%] Building CXX object CMakeFiles/pentest.dir/src/pentest.cpp.obj
[100%] Linking CXX executable bin/pentest.exe
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/pentest.dir/objects.a(pentest.cpp.obj): in function `main':
C:/projects/TheEndless/src/pentest.cpp:9: undefined reference to `theendless::model::Block::Block()'
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/projects/TheEndless/src/pentest.cpp:13: undefined reference to `theendless::model::Block::get_name[abi:cxx1c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/pentest.dir/objects.a(pentest.cpp.obj): in function `std::array<theendless::model::Block, 1ull>::array()':
c:/mingw/include/c++/9.2.0/array:94: undefined reference to `theendless::model::Block::Block()'
collect2.exe: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/pentest.dir/build.make:107: bin/pentest.exe] Error 1
make[1]: *** [CMakeFiles/Makefile2:124: CMakeFiles/pentest.dir/all] Error 2
make: *** [Makefile:103: all] Error 2
请注意,将 model/Block.cpp
包含到 pentest.cpp
会使问题消失,但我有点想让项目干净,所以我不想这样做。
附加信息:
- 我在 windows 10.
- 我正在使用 VSCode 编辑我的文件,我通过在集成的 *PowerShell 终端中键入
make
进行编译。 - 我正在使用 Mingw distro 进行编译。
如有任何帮助,我们将不胜感激! :)
我不是 CMake 或编译器专家,但我是这样理解发生了什么的。
编译器不会四处搜索任何头文件或源文件,除非它被告知必须这样做。但是编译器什么时候必须搜索它们?
- 文件(通常是文件头)包含在编译器已知的文件中。
- 该文件已明确为编译器所知(例如,在 CMakeLists.txt 中)。
在你的例子中,编译器知道头文件,因为它在 pentest.cpp 源文件中 #include
d(上面的变体 1)。编译器是怎么知道 pentest.cpp 的?在函数 add_executable
中明确指出特定构建目标 pentest
是从该文件构建的。
现在Block.cpp呢?编译器不知道,因为 CMakeLists.txt 中既没有包含也没有说明编译器必须使用此文件。所以编译器无法知道它。
正如您已经提到的,包含 .cpp 文件不是一个好的样式。仅包含头文件的一个巨大优势(在我看来)是,如果函数的实现发生变化(不是它的声明,而是函数的主体),您不必重新编译使用它的所有内容。您只需重新编译那个 .cpp 文件。
那么解决方法是什么?您必须让编译器知道应该用于构建目标的所有源文件 pentest
。因此,您必须将这些源文件添加到函数 add_executable
中。 CMake docs for add_executable告诉你以下内容。
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
Adds an executable target called to be built from the source files listed in the command invocation.
因此您必须将所有用于构建目标的源文件添加到相同的 add_executable
命令调用中。在您的情况下,CMakeLists.txt 看起来像这样。请注意,我必须在我的机器上添加 target_compile_features
并删除 add_compile_definitions
以强制使用 C++17。
cmake_minimum_required(VERSION 2.8.12)
project(TheEndless)
# add_definitions("-std=c++17") # does not work on my Windows 10 machine
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/model
)
link_directories(${CMAKE_SOURCE_DIR}/lib)
add_executable(main src/main.cpp)
# add all source files needed to build to the executable
# GameGrid.cpp is not needed because it is included in the header.
add_executable(pentest src/pentest.cpp src/model/Block.cpp)
target_link_libraries(main ${CONAN_LIBS})
target_link_libraries(pentest ${CONAN_LIBS})
target_compile_features(pentest PRIVATE cxx_std_17) # added to really use C++17, see the docs for explanation
我只看到“问题”:Visual Studio 将 Block.hpp、GameGrid.hpp 和 GameGrid.cpp 标记为“外部依赖项”。如果您希望它们显示为目标的一部分,您也可以将它们添加到 add_executable
。我不确定是否有其他解决方案,因为它似乎有点多余。