链接 CMake 项目时出现重复符号

问题描述 投票:0回答:1

我正在使用 CMake 按照 此模板构建一个项目。

链接阶段失败,并发现我的头文件中有重复的符号。

...
[100%] Linking CXX executable ../bin/user
duplicate symbol 'structures::BarStruct::dump()' in:
    ...build/src/CMakeFiles/user.dir/user.cpp.o
    libLearn.a[2](FooUser.cpp.o)
duplicate symbol 'structures::FooStruct::dump()' in:
    .../build/src/CMakeFiles/user.dir/user.cpp.o
    libLearn.a[2](FooUser.cpp.o)
duplicate symbol 'structures::FinalStruct::dump()' in:
    .../CMakeFiles/user.dir/user.cpp.o
    libLearn.a[2](FooUser.cpp.o)
ld: 3 duplicate symbols

我的项目布局如下。

├── CMakeLists.txt
├── README.md
├── cmake
│   └── Config.cmake.in
├── include
│   ├── FooHeader.hpp
│   └── FooUser.hpp
├── src
│   ├── CMakeLists.txt
│   ├── FooUser.cpp
│   └── user.cpp
└── tests

src/CMakeLists.txt

if(BUILD_TESTING)
    # Required for self registrering unit tests for a library target.
    set(PROJECT_TYPE OBJECT) 
else()
    # Without unit tests, build the target as a normal static library.*
    set(PROJECT_TYPE STATIC)
endif()

add_library(${PROJECT_NAME} ${PROJECT_TYPE})
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

target_sources(${PROJECT_NAME}
    PRIVATE
        FooUser.cpp
)

target_include_directories(${PROJECT_NAME}
    PUBLIC
        $<INSTALL_INTERFACE:include>/
        $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
        FooUser.cpp
)

target_include_directories(${PROJECT_NAME}
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}
)

add_executable(user user.cpp)

target_link_libraries(user PRIVATE ${PROJECT_NAME})

set_target_properties(user PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)

包括/FooHeader.hpp

#ifndef FOOHEADER_H
#define FOOHEADER_H

#include <iostream>

namespace structures {

struct FooStruct {
  std::vector<double> cost;
  std::vector<char> type;
  std::vector<int> count;
  void dump();
};

struct BarStruct {
  std::vector<double> cost;
  std::vector<char> count;
  void dump();
};

struct FinalStruct {
  std::vector<int64_t> numcust;
  std::vector<double> totalacctbal;
  void dump();
};
} // namespace

void structures::FooStruct::dump() {
  std::cout << "\nFOoO";
}

void structures::BarStruct::dump() {
  std::cout << "\nBar";
}

void structures::FinalStruct::dump() {
  std::cout << "\nFinal";
}

#endif //

包括/FooUser.hpp:

#ifndef FOOUSER_H
#define FOOUSER_H

#include "FooHeader.hpp"

namespace work {
  void headerFunc1(const structures::FooStruct& in, 
                   const structures::BarStruct& out);
  void headerFunc2(const structures::BarStruct& in, 
                         structures::FinalStruct& out);
} // namespace
  //

#endif

src/FooUser.cpp:

#include "FooHeader.hpp"
#include "FooUser.hpp"

void work::headerFunc1(const structures::FooStruct& in, 
                       const structures::BarStruct& ret) {
  std::cout << "Working on Foo -> Bar";
}

void work::headerFunc2(const structures::BarStruct& in, structures::FinalStruct& ret) {
  std::cout << "Working on Bar -> Final";
}

src/user.cpp

#include "FooUser.hpp"
#include <cassert>

bool verifyOutput(structures::FinalStruct &test) {
  test.dump();
  return test.numcust.size() > 0;
}

int main() {
  structures::FooStruct foo;
  structures::BarStruct bar;
  structures::FinalStruct finalstruct;

  work::headerFunc1(foo, bar);
  work::headerFunc2(bar, finalstruct);

  return 0;
}

CMakeLists.txt(顶层)

cmake_minimum_required(VERSION 3.13 FATAL_ERROR)

project(Learn CXX)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
add_subdirectory(src)

我理解这个错误,但我不确定我的 CMake 中的什么原因导致文件被多次包含。

我期待 ifndef 宏能够解决这个问题。我可以通过将文件压缩在一起来实现此目的,但我想学习如何将两个头文件分开。

平台:MacOS 15.2 和 Apple Clang。

c++ cmake build
1个回答
0
投票

现在是学习翻译单位概念的最佳时机。

翻译单元是编译器本身使用的单个单元,基本上是包含所有包含的头文件的单个源文件。编译器由此创建一个目标文件,链接器将使用该文件创建可执行程序。

函数(或变量)只能在一个翻译单元中定义(一个定义规则)。当您在头文件中定义函数时,它将在包含该头文件的所有翻译单元中定义,从而导致您的错误。

有两种解决方案:

  1. 将函数定义为一个源文件;
  2. 或将功能标记为
    inline
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.