我具有以下项目结构:
test_main.cc
#define CATCH_CONFIG_MAIN
#include "catch2.hpp"
test1.cc
#include "catch2.hpp"
#include "test_utils.hpp"
TEST_CASE("test1", "[test1]") {
REQUIRE(1 == 1);
}
test2.cc
#include "catch2.hpp"
#include "test_utils.hpp"
TEST_CASE("test2", "[test2]") {
REQUIRE(2 == 2);
}
test_utils.hpp
#pragma once
#include <iostream>
void something_great() {
std::cout << ":)\n";
}
如果我使用类似clang++ -std=c++17 test_main.cc test1.cc test2.cc
的东西进行编译,则在test1.o和test2.o中都定义了函数something_great
。这会导致类似
duplicate symbol __Z15something_greatv in:
test1.cc.o
test2.cc.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
在Catch2文档的Scaling Up部分中,他们提到为了分散测试,您可能需要
使用尽可能多的其他cpp文件(或任何您称为“ 实施文件),但需要进行分区 最适合您的工作方式。每个额外的文件需求 仅#include“ catch.hpp”
但是在examples section of the documentation中,我没有看到像我这样的用例。我读过this blog post,其中描述了三种不适合我的解决方案:将函数定义为宏,或者使函数static
或inline
。
是否存在另一种编译这些文件的方法,这些文件生成的单个可执行文件具有test_main.cc
定义的主要功能?
答案 0 :(得分:1)
这实际上与Catch或测试无关。当您#include
使用C ++编写的文件时,该文件会逐字复制到#include
行。如果将免费的函数定义放在标头中,则会在构建实际程序等时看到此问题。
潜在的问题是,#include
与大多数语言中的等效指令(import
,require
等)不是同一种import-a-module指令,可以在这种情况下执行明智的操作(确认标题与我们已经看到的标题相同,并且忽略重复的方法定义)。
建议您编写inline
的注释者在技术上是正确的,因为这将“解决您的问题”,因为编译器不会多次为该方法生成目标代码。但是,它并不能真正解释正在发生的事情或解决根本问题。
干净的解决方案是:
test_utils.hpp
中,将方法定义替换为方法声明:void something_great();
。test_utils.cc
(您目前在.hpp
中拥有该方法)。clang++ -std=c++17 test1.cc -c
clang++ -std=c++17 test2.cc -c
clang++ -std=c++17 test_main.cc -c
clang++ -std=c++17 test_utils.cc -c
clang++ -std=c++17 test1.o test2.o test_utils.o test_main.o
我还建议您阅读以下内容:What is the difference between a definition and a declaration?
明确地:
// test_utils.hpp
#pragma once
// This tells the compiler that when the final executable is linked,
// there will be a method named something_great which takes no arguments
// and returns void defined; the definition lives in test_utils.o in our
// case, although in practice the definition could live in any .o file
// in the final linking clang++ call.
void something_great();
并且:
// test_utils.cpp
#include "test_utils.hpp"
#include <iostream>
// Generates a DEFINITION for something_great, which
// will get put in test_utils.o.
void something_great() { std::cout << "Hi\n"; }
似乎您每次对测试进行更改时都会担心“重新编译Catch”。我讨厌把它交给您,但是您现在在C ++领域:您将毫无意义地重新编译东西。当包含头文件的库(例如Catch)在源文件发生更改时,必须在某种程度上“重新编译”,因为无论好坏,如果源文件或源文件中包含的头文件包括catch2.hpp
,则读取该源文件时,编译器将解析catch2.hpp
的源代码。
答案 1 :(得分:-1)
经过一些试验,我找到了一个合理的解决方案,它不需要您在对测试进行更改时就完全重新编译Catch。
以与以前相同的方式定义 test_main.cc :
#define CATCH_CONFIG_MAIN
#include "catch2.hpp"
添加另一个.cc文件, test_root ,其中将您的测试文件作为标头:
#include "test1.hpp"
#include "test2.hpp"
将测试源更改为标题:
test1.hpp
#pragma once
#include "catch2.hpp"
#include "test_utils.hpp"
TEST_CASE("test1", "[test1]") {
REQUIRE(1 == 1);
}
test2.hpp
#pragma once
#include "catch2.hpp"
#include "test_utils.hpp"
TEST_CASE("test2", "[test2]") {
REQUIRE(2 == 2);
}
单独编译
clang++ -std=c++17 test_main.cc -c
clang++ -std=c++17 test_root.cc -c
clang++ test_main.o test_root.o
其中test_main.cc仅需要编译一次。每当您更改测试时,都需要重新编译test_root.cc,当然,您必须重新链接两个目标文件。
如果有更好的解决方案,我暂时不会回答这个问题。