You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CMake编译静态库时render::ViewRenderer无符号的问题

C++模板类编译为静态库时出现未定义符号问题

我是CMake和复杂C++项目新手,尝试将多个类编译为静态库时,发现render::ViewRenderer类(声明于view.hpp,实现于view.cpp)在生成的librender.a中无对应符号。用clang-15编译,切换编译器后问题仍存在。render::WindowRenderer类编译正常,单独将ViewRenderer编译为库、直接编译到可执行文件均无效,仅将实现放入头文件可解决但不符合需求。链接时出现arm64架构下未定义符号错误,相关代码及构建日志如下:


相关代码

view.hpp

#ifndef VIEW_HPP
#define VIEW_HPP

#include <string>


namespace render {
    enum View {
        main_menu,
        game
    };
    /**
     * A class handling the rendering of a window's contents.
    */
    template <class View>
    class ViewRenderer {
        const View view;

    public:
        void render();

        ViewRenderer(View view);
        ~ViewRenderer();
    };
}

#endif

view.cpp

#include "render/view.hpp"


template <class View>
void render::ViewRenderer<View>::render() {

}


template <class View>
render::ViewRenderer<View>::ViewRenderer(View view) : view{ view } {

}

template <class View>
render::ViewRenderer<View>::~ViewRenderer() {

}

window.hpp

#ifndef WINDOW_HPP
#define WINDOW_HPP

#include <GLFW/glfw3.h>
#include <string>


namespace render {
    /**
     * A class to manage the application's windows. A window opens on init and blocks the thread with its execute method.
     * @param name the title of the window to open
     * @param game_renderer the initial GameRenderer to handle the window's contents or nullptr for default behaviour
    */
    enum WindowMode { windowed, fullscreen, windowed_fullscreen };
    class WindowRenderer {
        const char* window_name;
        GLFWwindow* window;
        render::WindowMode window_mode;

        void execute();
    public:
        void (*set_fullscreen)(WindowMode);
        void (*end)();

        WindowRenderer(std::string);
        ~WindowRenderer();
    };
}

#endif

window.cpp

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <string>
#include <stdexcept>

#include "render/window.hpp"

render::WindowRenderer::WindowRenderer(std::string _window_name) : window_name{ _window_name.c_str() }, window_mode{ render::fullscreen } {
    bool glewExperimental = true;
    if (!glfwInit()) {
        throw std::runtime_error("Failed to initialize GLFW\n"); return;
    }

    GLFWmonitor* monitor = glfwGetPrimaryMonitor();
    this->window = glfwCreateWindow(640, 480, this->window_name, NULL, NULL);
    glfwMakeContextCurrent(window);

    if (glewInit() != GLEW_OK) {
        throw std::runtime_error("Failed to initialize GLEW\n"); return;
    }

    this->execute();
}

void render::WindowRenderer::execute() {
    do {
        glClear(GL_COLOR_BUFFER_BIT);

        glfwSwapBuffers(window);
        glfwPollEvents();
    } while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose(window) == 0);
}
render::WindowRenderer::~WindowRenderer() {

}

CMakeLists.txt

set(CMAKE_C_COMPILER /opt/local/bin/clang-mp-15)
set(CMAKE_CXX_COMPILER /opt/local/bin/clang++-mp-15)


cmake_policy(SET CMP0048 NEW)
cmake_minimum_required(VERSION 3.20)

if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(OPENGL_LIBRARY
    ${OPENGL_LIBRARY}
    -lGL -lGLU -lXrandr -lXext -lX11 -lrt
    ${CMAKE_DL_LIBS}
    ${GLFW_LIBRARIES}
)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(OPENGL_LIBRARY
    ${OPENGL_LIBRARY}
    ${CMAKE_DL_LIBS}
    ${GLFW_LIBRARIES}
)
endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")

add_definitions(
    -DTW_STATIC
    -DTW_NO_LIB_PRAGMA
    -DTW_NO_DIRECT3D
    -DGLEW_STATIC
    -D_CRT_SECURE_NO_WARNINGS
)


project(
    game
    VERSION 0.0.1
    LANGUAGES CXX C OBJC OBJCXX
)



include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++20" COMPILER_SUPPORTS_CXX20)
if(COMPILER_SUPPORTS_CXX20)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
else()
        message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++20 support. Please use a different C++ compiler.")
endif()

# ---- Libraries ----


set(SOURCE
    ${CMAKE_SOURCE_DIR}/src/app/render/window.cpp    
    ${CMAKE_SOURCE_DIR}/src/app/include/render/window.hpp    
    ${CMAKE_SOURCE_DIR}/src/app/render/view.cpp    
    ${CMAKE_SOURCE_DIR}/src/app/include/render/view.hpp    
)



# ---- External libraries ----
set(GLEW_VERSION 2.1.0)
set (GLFW_VERSION 3.3.8)
set(GLM_VERSION 0.9.9.8)
# >>>>>>>>>>>>>>>>>>>>>>>> GLEW
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/glew-${GLEW_VERSION}/build/cmake) #this produces glew target
set(GLEW_INCLUDE ${CMAKE_SOURCE_DIR}/lib/glew-${GLEW_VERSION}/include)
# >>>>>>>>>>>>>>>>>>>>>>>> GLFW
add_subdirectory(lib/glfw-${GLFW_VERSION})
SET(GLFW_INCLUDE lib/glfw-${GLFW_VERSION}/include)
# >>>>>>>>>>>>>>>>>>>>>>>> GLM
SET(GLM_INCLUDE lib/glm-${GLM_VERSION})
# -----------------------------


include_directories(
    ${CMAKE_SOURCE_DIR}/src/app/include
    ${GLEW_INCLUDE}
    ${GLFW_INCLUDE}
    ${GLM_INCLUDE}
)


add_executable(
    game_exe
    ${CMAKE_SOURCE_DIR}/src/app/main.cpp
)
set(TARGET game_exe PROPERTY CMAKE_CXX_STANDARD 20)

add_library(
    render 
    ${CMAKE_SOURCE_DIR}/src/app/render/window.cpp
    ${CMAKE_SOURCE_DIR}/src/app/include/render/window.hpp
    ${CMAKE_SOURCE_DIR}/src/app/render/view.cpp
    ${CMAKE_SOURCE_DIR}/src/app/include/render/view.hpp
)

target_link_libraries(
    game_exe
    render
    glfw
    glew
)

项目结构

app
    ...
    src
        include
            render
                view.hpp
                window.hpp
        render
            view.cpp
            window.cpp
        main.cpp
    ...
CMakeLists.txt

构建错误信息

[main] Building folder: game 
[build] Starting build
[proc] Executing command: /opt/homebrew/bin/cmake --build /Users/matt/projects/game/build --config Debug --target all --
[build] [3/5  20% :: 0.445] Building CXX object CMakeFiles/render.dir/src/app/render/view.cpp.o
[build] [3/5  40% :: 0.447] Building CXX object CMakeFiles/render.dir/src/app/render/window.cpp.o
[build] [4/5  60% :: 0.448] Building CXX object CMakeFiles/game_exe.dir/src/app/main.cpp.o
[build] [4/5  80% :: 0.474] Linking CXX static library librender.a
[build] [5/5 100% :: 0.619] Linking CXX executable game_exe
[build] FAILED: game_exe 
[build] : && /opt/local/bin/clang++-mp-15 -std=c++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.0.sdk -Wl,-search_paths_first -Wl,-headerpad_max_install_names  CMakeFiles/game_exe.dir/src/app/main.cpp.o -o game_exe  -Wl,-rpath,/Users/matt/projects/game/build/lib  librender.a  lib/glfw-3.3.8/src/libglfw3.a  lib/libGLEW.2.1.0.dylib  -framework Cocoa  -framework IOKit  -framework CoreFoundation  -Xlinker -framework -Xlinker OpenGL && :
[build] Undefined symbols for architecture arm64:
[build]   "render::ViewRenderer<render::View>::ViewRenderer(render::View)", referenced from:
[build]       _main in main.cpp.o
[build]   "render::ViewRenderer<render::View>::~ViewRenderer()", referenced from:
[build]       _main in main.cpp.o
[build] ld: symbol(s) not found for architecture arm64
[build] clang: error: linker command failed with exit code 1 (use -v to see invocation)
[build] ninja: build stopped: subcommand failed.
[proc] The command: /opt/homebrew/bin/cmake --build /Users/matt/projects/game/build --config Debug --target all -- exited with code: 1 and signal: null
[build] Build finished with exit code 1
[cpptools] The build configurations generated do not contain the active build configuration. Using "" for CMAKE_BUILD_TYPE instead of "Debug" to ensure that IntelliSense configurations can be found

问题原因及解决方案

问题根源

C++模板是代码生成机制,编译器只有在看到模板的具体实例化代码时,才会生成对应参数的机器码。当你把ViewRenderer的实现放在view.cpp中时,编译这个文件的阶段,编译器不知道你后续会用render::View作为模板参数来实例化类,所以不会生成ViewRenderer<render::View>的构造、析构及render方法的代码,导致静态库librender.a中没有这些符号,链接可执行文件时就会报错。

可行解决方案

1. 显式实例化模板(推荐)

在view.cpp的末尾添加显式实例化代码,告诉编译器生成特定模板参数的类实例:

// 在view.cpp最后添加
template class render::ViewRenderer<render::View>;

这样编译view.cpp时,编译器就会生成ViewRenderer<render::View>的所有成员函数代码,静态库中就会包含对应的符号,链接时就能找到。

2. 将模板实现移至头文件

这是C++模板最通用的写法,把成员函数的实现直接放在头文件的类定义中,或者头文件的末尾。比如修改view.hpp:

// view.hpp中修改类定义
template <class View>
class ViewRenderer {
    const View view;

public:
    void render() {
        // 实现代码
    }

    ViewRenderer(View view) : view{ view } {
        // 实现代码
    }
    ~ViewRenderer() {
        // 实现代码
    }
};

这样任何包含该头文件的代码在实例化模板时,编译器都能获取到实现并生成对应代码。

3. 包含实现文件的技巧

在view.hpp的末尾添加:

// view.hpp最后一行
#include "../render/view.cpp"

同时修改view.cpp,添加保护宏防止重复编译:

#ifndef VIEW_CPP_IMPL
#define VIEW_CPP_IMPL

// 原view.cpp的所有代码

#endif // VIEW_CPP_IMPL

这样包含头文件的代码会自动引入实现,编译器能生成实例代码,同时单独编译view.cpp时也不会出现重复定义问题。


内容的提问来源于stack exchange,提问作者MattC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:35:19