c++ - CMake + GoogleTest 在小型库集合中给出重新定义错误

标签 c++ cmake googletest

<分区>

我收集了我们在学校使用的多个非常小的图书馆。每个库都与谷歌测试文件一起放在自己的文件夹中。这使我能够在各种作业中使用这些功能。这些库有时相互依赖。

我正在尝试使用 CMake 构建单个测试可执行文件,它结合了所有库的测试,因此我可以检测何时更改了库本身或使用更改后的库的其他库中的某些行为。不幸的是,我在挣扎:有一个库 csv 和另一个使用该 csv 库的库 sortvector 。当运行由 CMake 生成的 make 时,这会以某种方式给出重新定义错误,但我不明白为什么以及如何解决这个问题。

项目结构(代码可以在这里找到:https://github.com/MartenBE/algoritmen):

.
├── chrono
│   ├── chrono-simple.cpp
│   ├── chrono-simple.h
│   └── chrono-simple-test.cpp
├── CMakeLists.txt
├── CMakeLists.txt.in
├── csv
│   ├── csv.h
│   └── csv-test.cpp
├── generate_makefile.sh
├── intstring
│   ├── intstring.h
│   └── intstring-test.cpp
├── README.md
└── sorteren
    ├── sorteermethode.h
    ├── sorteermethode-test.cpp
    ├── sortvector.h
    └── sortvector-test.cpp

如何运行

./generate_makefile.sh
cd build
make
./unittest

CMake 文件将直接从此处指定的 github 拉取 googletest(这是根目录中 CMakeLists 的底部,有一些修改):https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project

运行make时的错误输出:

-- 配置完成

-- Generating done
-- Build files have been written to: /home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download
gmake[1]: Entering directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
gmake[2]: Entering directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
gmake[3]: Entering directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
gmake[3]: Leaving directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
gmake[3]: Entering directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
[ 11%] Performing update step for 'googletest'
Current branch master is up to date.
[ 22%] No configure step for 'googletest'
[ 33%] No build step for 'googletest'
[ 44%] No install step for 'googletest'
[ 55%] No test step for 'googletest'
[ 66%] Completed 'googletest'
gmake[3]: Leaving directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
[100%] Built target googletest
gmake[2]: Leaving directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
gmake[1]: Leaving directory '/home/martijn/git/Algoritmen-I/labos/libraries/build/googletest-download'
Created test "unittest"
-- Configuring done
-- Generating done
-- Build files have been written to: /home/martijn/git/Algoritmen-I/labos/libraries/build
[ 16%] Built target gtest
[ 33%] Built target gtest_main
[ 50%] Built target gmock
Scanning dependencies of target unittest
[ 58%] Linking CXX executable unittest
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o: in function `CsvData::voeg_data_toe(std::vector<double, std::allocator<double> > const&)':
/home/martijn/git/Algoritmen-I/labos/libraries/sorteren/../csv/csv.h:83: multiple definition of `CsvData::voeg_data_toe(std::vector<double, std::allocator<double> > const&)'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:/home/martijn/git/Algoritmen-I/labos/libraries/csv/csv.h:83: first defined here
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o:(.bss+0x8): multiple definition of `CsvData::extensie[abi:cxx11]'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:(.bss+0x8): first defined here
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o: in function `testing::Test::TearDownTestSuite()':
/home/martijn/git/Algoritmen-I/labos/libraries/sorteren/../csv/csv.h:49: multiple definition of `CsvData::CsvData(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char, char)'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:/home/martijn/git/Algoritmen-I/labos/libraries/csv/csv.h:49: first defined here
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o: in function `testing::Test::TearDownTestSuite()':
/home/martijn/git/Algoritmen-I/labos/libraries/sorteren/../csv/csv.h:49: multiple definition of `CsvData::CsvData(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char, char)'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:/home/martijn/git/Algoritmen-I/labos/libraries/csv/csv.h:49: first defined here
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o: in function `CsvData::write_to_file() const':
/home/martijn/git/Algoritmen-I/labos/libraries/sorteren/../csv/csv.h:129: multiple definition of `CsvData::write_to_file() const'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:/home/martijn/git/Algoritmen-I/labos/libraries/csv/csv.h:129: first defined here
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o: in function `CsvData::geef_bestandsnaam[abi:cxx11]() const':
/home/martijn/git/Algoritmen-I/labos/libraries/sorteren/../csv/csv.h:124: multiple definition of `CsvData::geef_bestandsnaam[abi:cxx11]() const'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:/home/martijn/git/Algoritmen-I/labos/libraries/csv/csv.h:124: first defined here
/usr/bin/ld: CMakeFiles/unittest.dir/sorteren/sorteermethode-test.cpp.o: in function `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&)':
/home/martijn/git/Algoritmen-I/labos/libraries/sorteren/../csv/csv.h:94: multiple definition of `CsvData::to_string[abi:cxx11]() const'; CMakeFiles/unittest.dir/csv/csv-test.cpp.o:/home/martijn/git/Algoritmen-I/labos/libraries/csv/csv.h:94: first defined here
clang-7: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [CMakeFiles/unittest.dir/build.make:147: unittest] Error 1
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/unittest.dir/all] Error 2
make: *** [Makefile:141: all] Error 2

如何解决这些问题?有没有更好的方法来构建这样一个由小型图书馆组成的项目?我真的更喜欢将每个库保存在自己的子文件夹中。

PS:我现在的代码不是很漂亮,它是给我们的,我正在重构它,所以它仍然很乱,没有达到最佳实践。


CMakeLists.txt

set(SOURCES
    )

set(ADDITIONAL_INPUT_FILES
    )

set(TEST_SOURCES
    chrono/chrono-simple-test.cpp
    csv/csv-test.cpp
    intstring/intstring-test.cpp
    sorteren/sorteermethode-test.cpp
    sorteren/sortvector-test.cpp
    )

set(ADDITIONAL_TEST_FILES
    )

### executable #################################################################

cmake_minimum_required(VERSION 3.9)
project (proj)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_BUILD_TYPE debug)

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra -O0")
    # set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,-boost-*,-google-*,-llvm-*;-header-filter=.*")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
endif()

if (SOURCES)
    foreach(FILE ${ADDITIONAL_INPUT_FILES})
        message("Copying file ${FILE}")
        configure_file(${FILE} ${FILE} COPYONLY)
    endforeach(FILE)

    add_executable(${PROJECT_NAME} ${SOURCES})
endif()

### unittest ###################################################################

if (TEST_SOURCES)
    # Download and unpack googletest at configure time
    configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
    execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
        RESULT_VARIABLE result
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)

    if(result)
        message(FATAL_ERROR "CMake step for googletest failed: ${result}")
    endif()

    execute_process(COMMAND ${CMAKE_COMMAND} --build .
        RESULT_VARIABLE result
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)

    if(result)
        message(FATAL_ERROR "Build step for googletest failed: ${result}")
    endif()

    # Prevent overriding the parent project's compiler/linker
    # settings on Windows
    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

    # Add googletest directly to our build. This defines
    # the gtest and gtest_main targets.
    add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
        ${CMAKE_BINARY_DIR}/googletest-build
        EXCLUDE_FROM_ALL)

    # The gtest/gtest_main targets carry header search path
    # dependencies automatically when using CMake 2.8.11 or
    # later. Otherwise we have to add them here ourselves.
    if (CMAKE_VERSION VERSION_LESS 2.8.11)
        include_directories("${gtest_SOURCE_DIR}/include")
    endif()

    foreach(FILE ${ADDITIONAL_TEST_FILES})
        message("Copying file ${FILE}")
        get_filename_component(FILE_NAME ${FILE} NAME)
        configure_file(${FILE} ${FILE_NAME} COPYONLY)
    endforeach(FILE)

    enable_testing()

    # Create the build executable
    set(TEST_EXECUTABLE_NAME "unittest")
    add_executable(${TEST_EXECUTABLE_NAME} ${TEST_SOURCES})
    target_link_libraries(${TEST_EXECUTABLE_NAME} gtest_main gmock)
    add_test("${TEST_EXECUTABLE_NAME}_ctest" ${TEST_EXECUTABLE_NAME})
    message("Created test \"${TEST_EXECUTABLE_NAME}\"")
endif()

csv.h:

#ifndef CSV_H
#define CSV_H

#include <algorithm>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

class CsvData
{
public:
    // Scheidingsteken: teken in vlottendekommagetallen
    // Voor een Nederlandstalige excel, scheidingsteken ',' opgeven
    CsvData(const std::string& bestandsnaam, char scheidingsteken = '.', char delimiter = '\t');

    template <class T> // T kan int, unsigned int, float, ... zijn
    void voeg_data_toe(const std::vector<T>& toe_te_voegen_data);

    void voeg_data_toe(const std::vector<double>& nieuwe_data);

    std::string to_string() const;

    std::string geef_bestandsnaam() const;
    void write_to_file() const;

protected:
    std::vector<std::vector<double>> data;
    char scheidingsteken;
    char delimiter;
    std::string bestandsnaam;
    int max_kolom_grootte = 0;

    static constexpr int kolombreedte = 12;
    static constexpr int precisie = 6;
    static const std::string extensie;
};

const std::string CsvData::extensie{".csv"};

// Scheidingsteken: teken in vlottendekommagetallen
// Voor een Nederlandstalige excel, scheidingsteken ',' opgeven
CsvData::CsvData(const std::string& bestandsnaam, char scheidingsteken, char delimiter)
: scheidingsteken{scheidingsteken}, delimiter{delimiter}, bestandsnaam{bestandsnaam}
{
    if (bestandsnaam.empty())
    {
        throw "Lege bestandsnaam";
    }

    int begin_extensie = bestandsnaam.rfind(extensie);

    if (begin_extensie == 0)
    {
        throw "Ongeldige bestandsnaam";
    }

    if ((begin_extensie == std::string::npos) || (begin_extensie != (bestandsnaam.size() - extensie.size())))
    {
        this->bestandsnaam.append(extensie);
    }
}

template <class T> // T kan int, unsigned int, float, ... zijn
void CsvData::voeg_data_toe(const std::vector<T>& toe_te_voegen_data)
{
    std::vector<double> nieuwe_data;
    nieuwe_data.reserve(toe_te_voegen_data.size());

    for (const T& d : toe_te_voegen_data)
    {
        nieuwe_data.push_back(static_cast<double>(d));
    }

    voeg_data_toe(nieuwe_data);
}

void CsvData::voeg_data_toe(const std::vector<double>& nieuwe_data)
{
    data.push_back(nieuwe_data);

    int kolom_grootte = nieuwe_data.size();
    if (kolom_grootte > max_kolom_grootte)
    {
        max_kolom_grootte = kolom_grootte;
    }
}

std::string CsvData::to_string() const
{
    std::stringstream out;

    for (int i = 0; i < max_kolom_grootte; i++)
    {
        for (int j = 0; j < data.size(); j++)
        {
            if (i < data[j].size())
            {
                out << std::setprecision(precisie) << std::scientific << data[j][i];
            }

            if (j == (data.size() - 1))
            {
                out << '\n';
            }
            else
            {
                out << delimiter;
            }
        }
    }

    std::string content = out.str();
    std::replace(content.begin(), content.end(), '.', scheidingsteken);

    return content;
}

std::string CsvData::geef_bestandsnaam() const
{
    return bestandsnaam;
}

void CsvData::write_to_file() const
{
    std::ofstream out(bestandsnaam);
    assert(out);

    out << to_string();
}

#endif

排序 vector .h

#ifndef SORTVECTOR_H
#define SORTVECTOR_H

/**
 \class sortvector
 \brief is een klasse van sorteerbare vectoren
 Bovenop de vectoreigenschappen zijn er hulpfuncties om sorteervergelijkingen
 te vergemakkelijken.
*/

#include <algorithm>
#include <cassert>
#include <iomanip>
#include <iostream>
#include <limits>
#include <random>
#include <vector>

template <class T>
class Sortvector : public std::vector<T>
{
public:
    /// \fn Constructor: het argument geeft de grootte aan
    /// bij constructie zal de tabel opgevuld worden met
    /// n verschillende Ts in random volgorde
    /// (zie hulplidfuncties)
    Sortvector(int);
    Sortvector(const Sortvector<T>& v) = delete;
    Sortvector<T>& operator=(const Sortvector<T>& v) = delete;
    Sortvector<T>& operator=(Sortvector<T>&& v) = delete;
    Sortvector(Sortvector<T>&& v) = delete;

    /// \fn vul_range vul vector met de waarden T(0)...T(size()-1) in volgorde
    void vul_range();
    void draai_om();
    void vul_omgekeerd();
    void shuffle();
    void vul_random_zonder_dubbels();
    void vul_random(); //< nog niet implementeren

    bool is_gesorteerd() const;
    /// \fn is_range controleert of *this eruit ziet als het resultaat van vul_range(), d.w.z.
    /// dat, voor alle i, (*this)[i]==T(i);
    bool is_range() const;

    friend std::ostream& operator<<(std::ostream& os, const Sortvector<T>& s)
    {
        s.schrijf(os);
        return os;
    }

private:
    void schrijf(std::ostream& os) const;
};

template <class T>
Sortvector<T>::Sortvector(int grootte) : std::vector<T>(grootte)
{
    vul_random();
}

template <class T>
void Sortvector<T>::vul_range()
{
    T waarde{0};

    for (auto& data : (*this))
    {
        data = waarde;
        waarde++;
    }
}

template <class T>
void Sortvector<T>::draai_om()
{
    std::reverse(this->begin(), this->end());
}

template <class T>
void Sortvector<T>::shuffle()
{
    std::random_device rd;
    std::mt19937 eng{rd()};

    std::shuffle(this->begin(), this->end(), eng);
}

template <class T>
void Sortvector<T>::vul_omgekeerd()
{
    T waarde{0};

    std::for_each(this->rbegin(), this->rend(), [&](T& data) {
        data = waarde;
        waarde++;
    });
}

template <class T>
void Sortvector<T>::vul_random_zonder_dubbels()
{
    vul_range();
    shuffle();
}

template <class T>
void Sortvector<T>::vul_random()
{
    std::random_device rd;
    std::mt19937 eng{rd()};
    assert((this->size() - 1) < std::numeric_limits<int>::max());
    std::uniform_int_distribution<int> dist{0, static_cast<int>(this->size() - 1)};

    for (auto& i : (*this))
    {
        i = dist(rd);
    }
}

template <class T>
bool Sortvector<T>::is_gesorteerd() const
{
    if (this->size() <= 1)
    {
        return true;
    }

    for (int i = 1; i < this->size(); i++)
    {
        if ((*this)[i - 1] > (*this)[i])
        {
            return false;
        }
    }

    return true;
}

template <class T>
bool Sortvector<T>::is_range() const
{
    if (this->size() <= 1)
    {
        return true;
    }

    for (int i = 1; i < this->size(); i++)
    {
        if ((*this)[i] != ((*this)[i - 1] + 1))
        {
            return false;
        }
    }

    return true;
}

template <class T>
void Sortvector<T>::schrijf(std::ostream& os) const
{
    for (auto&& t : *this)
    {
        os << t << " ";
    }
    os << "\n";
}

#endif

最佳答案

您需要将实现放在 csv.cpp 文件中,模板除外:

const std::string CsvData::extensie{".csv"};

// Scheidingsteken: teken in vlottendekommagetallen
// Voor een Nederlandstalige excel, scheidingsteken ',' opgeven
CsvData::CsvData(const std::string& bestandsnaam, char scheidingsteken, char delimiter)
: scheidingsteken{scheidingsteken}, delimiter{delimiter}, bestandsnaam{bestandsnaam}
{
    if (bestandsnaam.empty())
    {
        throw "Lege bestandsnaam";
    }

    int begin_extensie = bestandsnaam.rfind(extensie);

    if (begin_extensie == 0)
    {
        throw "Ongeldige bestandsnaam";
    }

    if ((begin_extensie == std::string::npos) || (begin_extensie != (bestandsnaam.size() - extensie.size())))
    {
        this->bestandsnaam.append(extensie);
    }
}

void CsvData::voeg_data_toe(const std::vector<double>& nieuwe_data)
{
    data.push_back(nieuwe_data);

    int kolom_grootte = nieuwe_data.size();
    if (kolom_grootte > max_kolom_grootte)
    {
        max_kolom_grootte = kolom_grootte;
    }
}

std::string CsvData::to_string() const
{
    std::stringstream out;

    for (int i = 0; i < max_kolom_grootte; i++)
    {
        for (int j = 0; j < data.size(); j++)
        {
            if (i < data[j].size())
            {
                out << std::setprecision(precisie) << std::scientific << data[j][i];
            }

            if (j == (data.size() - 1))
            {
                out << '\n';
            }
            else
            {
                out << delimiter;
            }
        }
    }

    std::string content = out.str();
    std::replace(content.begin(), content.end(), '.', scheidingsteken);

    return content;
}

std::string CsvData::geef_bestandsnaam() const
{
    return bestandsnaam;
}

void CsvData::write_to_file() const
{
    std::ofstream out(bestandsnaam);
    assert(out);

    out << to_string();
}

关于c++ - CMake + GoogleTest 在小型库集合中给出重新定义错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55020779/

相关文章:

c++ - 错误1错误LNK1104 : cannot open file 'winmm.lib'

dll - 在 Windows 上的 CLion/CMake 中,APP.exe 如何找到 lib/A.dll 以便运行?

c++ - MFC gui 可以用谷歌测试测试吗?

build - 如何为其他目录添加包含规则?

c++ - CMake:将 GTest 添加到构建中

c++ - 在谷歌测试框架中,如何期待一个函数调用或另一个函数调用?

C++函数钩子(Hook)(dll, asm)

c++ - 避免沿并行层次结构向下转换值

c++ - 在 Linux 上测试的工作代码在 win 7、Visual Studio 12 上抛出 'error C3646'

c++ - 使用 CLion IDE 运行 Opengl 程序