
这里已经给出了提示了,终端的环境变量需要重启以后才能更新
FrozenLemonTee
-
dx2命令无效 -
在CLion 2025.3.2中配置xmake项目@SPeak 具体没研究过,不过JetBrains的IDE集成程度比vscode高,可能不太能通过命令或者修改配置文件的方式来修改。如果有朋友有相关了解的可以贴在这里。
-
在CLion 2025.3.2中配置xmake项目@SPeak CLion中的工具链配置主要是为了代码提示,如果使用默认工具链MinGW,有些代码提示可能会有问题,比如标准库的
import std可能会冒红线。 -
在CLion 2025.3.2中配置xmake项目鉴于MCPP社区中各种C++项目依赖XMake进行配置,并且XMake缺乏足够多的文档教程的情况下,有必要写一篇使用IDE配置项目的教程。
环境:
- Windows 11
- CLion 2025.3.2
- XMake 3.0.6
- Microsoft Visual C++ 14.44.35207
请确保系统环境符合以上条件。
第一步,通过CLion 2025.3.2打开一个XMake项目(必须是一个合法的XMake项目,即项目根目录需要一个
xmake.lua配置文件)。

第二步,依次点击左上角的 文件 - 设置 - 构建、执行、部署 - 工具链,点击左上角的 + 按钮(添加),添加Visual Studio配置,并且将该配置置顶,设为默认。

第三步,依次点击左上角的 文件 - 设置 - 插件,搜索XMake插件并安装。

第四步,点击右上角的 添加配置 ,打开 运行/调试配置 页面,点击左上角的 + 按钮(添加新配置),选择 XMake,然后设置XMake工具包路径和工具链类型(MSVC/Microsoft Visual C++),构建目标和构建模式此处为了演示,设置为
default和release。

第五步,点击右上角的三角按钮,运行项目,项目会正常构建和运行,并最后输出结果。

第六步,在CLion中打开终端,输入下面的命令,在项目根目录生成一个
compile_commands.json文件。xmake project -k compile_commands
第七步,在项目左侧导航栏中找到
compile_commands.json文件,单击以后,点击右上角的 修正... - 加载编译数据库项目。

Note:导入成功会提醒。

此时,项目的代码高亮、导航、提示功能会被启用。

项目中识别出的目标源代码和依赖代码文件的图标左上角会显示一个蓝色小方块,表明此时XMake项目已经被CLion识别。

-
C++中的initializer_list获取列表长度并用于模板参数的编译期计算小示例clang最小可编译的版本和标准是clang3.4.1+cpp11:

-
项目代码在gtest单元测试中崩溃的问题原问题发布在我项目的issue专栏中:
https://github.com/FrozenLemonTee/original/issues/9具体细节请看原问题,此处不过多赘述。
大致情况就是我在开启cmake Release优化后,对我的基础库项目中的双向链表使用gtest进行单元测试时发生崩溃。而且错误仅在对双向链表的相等运算符进行单元测试时发生,该源文件中仅有这一项测试会崩溃,同时具有相似实现的单向链表也有此问题。
补充一些环境信息:
发生崩溃的现象本人已在以下环境复现:- Manjaro Linux+CPP23+GCC15.1+Google Test v1.16.0
- Windows11+CPP23+MinGW 11.0 w64+Google Test v1.16.0
-
现代C++项目开发初识——记项目Orignal大改造写在前面
这是本人在D2Learn的第一篇Blog,敬请指教~
本文项目地址:https://github.com/FrozenLemonTee/original
欢迎提出宝贵意见,如issue和pull request~
项目简介
目前在写的Original是一个仿照C++ STL的基础工具库,是几年前在复习考研时的项目data_structure_in_408的重制升级版。当时对于现代C++标准还知之甚少,甚至面向对象的内容掌握的也一般,所以造成的结果是用C++语言写出了C风格的代码:
因此在Original中果断摈弃了纯C的代码,大量采用现代C++语言标准(如:命名空间、auto、constexpr、mutable、using别名、const成员方法、列表式构造初始化、条件if编译、左值引用等)、模板编程(泛化与特化、CRTP、嵌套模板等)、面向对象(特殊构造方法、接口复用、虚函数继承与重写、友元与封装设计、多态基类引用和指针等),以充分满足项目业务需求,同时保证项目代码有足够的可读性和可维护性。改造起因
以上是项目相对于老项目的更新与改造,然而最近也学习到了现代项目更多的维护与管理方法。这里特别感谢给我项目提出Issue的朋友,链接点此处:原文.
文中暴露了原本我项目存在的一些问题:源码文件随意堆放

层次不清,不利于管理。CMake配置文件过于简单
cmake_minimum_required(VERSION 3.29) project(original) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) include_directories(${CMAKE_SOURCE_DIR}) add_executable(test1 test/test1.cpp original.h) add_executable(test2 test/test2.cpp original.h) add_executable(test3 test/test3.cpp original.h)此处的问题也是不方便管理项目,过于扁平化。
文档及教程缺少展示
仅仅只是将
docs文件夹置于项目仓库中,并没有在README.md中将文档文件渲染展示出来。没有一个好的方法让其他开发者能够快速引入项目库的代码,需要其他开发者自己配置,增大学习交流和协同开发的成本。手写测试

测试可读性差,不便于比对测试结果。改造方案
针对上述问题,我对项目进行了相关的调整与改造:
源码结构调整
源码文件夹如下所示:

其中original.h是项目主头文件,会将每个模块的主头文件包含在内:#ifndef ORIGINAL_H #define ORIGINAL_H #include "core/core.h" // other modules etc... #endif //ORIGINAL_H每个模块的主头文件则包含了除自己以外该模块文件夹下的所有源文件:
#ifndef CORE_H #define CORE_H #include "algorithms.h" #include "array.h" #include "bitSet.h" #include "blocksList.h" #include "chain.h" #include "cloneable.h" #include "comparator.h" #include "container.h" #include "couple.h" #include "deque.h" #include "doubleDirectionIterator.h" #include "error.h" #include "filter.h" #include "filterStream.h" #include "forwardChain.h" #include "iterable.h" #include "iterationStream.h" #include "iterator.h" #include "maths.h" #include "printable.h" #include "prique.h" #include "queue.h" #include "randomAccessIterator.h" #include "serial.h" #include "stack.h" #include "stepIterator.h" #include "transform.h" #include "transformStream.h" #include "vector.h" #include "wrapper.h" #endif //CORE_H
这样给予用户充分的选择权,可以直接引入original.h项目主头文件免去引入其他任何头文件的麻烦,或者为了避免编译时间过长,引入如#include "vector.h"这样单个头文件也能成为可能,以及以上两者取其中而引入某个模块的头文件#include "core.h".部署在线文档
文档链接:文档-Original

在README.md中将其展示。首先编辑
Doxyfile配置文件:# 项目名称 PROJECT_NAME = "ORIGINAL" # 源目录 INPUT = src # 输出目录 OUTPUT_DIRECTORY = ../original_docs/docs # 文件类型 FILE_PATTERNS = *.h # 允许递归扫描源文件夹 RECURSIVE = YES # 启用UML类图生成 HAVE_DOT = YES CLASS_DIAGRAMS = YES # 生成PlantUML文件 GENERATE_UML = YES UML_LOOK = YES UML_LIMIT_NUM_CLASSES = 0 # 设置类图的类数量限制 # 使用PlantUML格式输出 UML_OUTPUT_FORMAT = plantuml其中项目名称指定生成的文档所显示的项目名称,源目录和文件类型指定为哪些代码生成文档,递归扫描配合项目的层级结构使用,以避免遗漏层级中的某些文件,输出目录的设置是将文档生成在项目本地文件夹的隔壁文件夹里,方便利用git工具进行版本管理和在线部署。
在目录下输入以下命令即可生成文档静态网页文件:
doxygen输出的项目文件夹如下:

将其同步到github仓库后,我们使用Vercel平台在线部署我们的文档:

"Framework Preset"的框架处选择“其他”,因为Doxygen生成的是静态网页,根目录选择docs/html,因为项目主页文件index.html在此文件夹下。点击部署按钮即可完成在线文档的部署。Cmake配置构建结构调整
此处的调整为这次调整的核心内容,极大地方便了项目的构建和分发。在便于项目管理的同时,增加了库的构建和引入方法:

主要改动是:
配置主CmakeLists.txt:cmake_minimum_required(VERSION 3.30) project(original LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_BUILD_TYPE Debug) include_directories(${CMAKE_SOURCE_DIR}) include_directories(${CMAKE_SOURCE_DIR}/src/core) file(GLOB CORE_HEADERS "${CMAKE_SOURCE_DIR}/src/original.h") add_library(original STATIC ${CORE_HEADERS} src/original.cpp) install(TARGETS original DESTINATION lib) install(FILES src/original.h DESTINATION include) install(DIRECTORY src/core/ DESTINATION include/core FILES_MATCHING PATTERN "*.h") set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install) include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/originalConfig.cmake.in" "${CMAKE_BINARY_DIR}/cmake/originalConfig.cmake" INSTALL_DESTINATION cmake/original ) install(FILES "${CMAKE_BINARY_DIR}/cmake/originalConfig.cmake" DESTINATION cmake/original) # test cases add_subdirectory(test/legacy) add_subdirectory(test/unit_test)其中
include_directories相关命令让cmake能够找到项目源码所在的目录,使得接下来的命令操作成为可能。file命令在于打包项目主头文件,以便找到项目所有的头文件。add_library处创建一个可执行文件用于构建动态链接文件。install命令则指定安装时将所有的头文件都正确地被复制。set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install) include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/originalConfig.cmake.in" "${CMAKE_BINARY_DIR}/cmake/originalConfig.cmake" INSTALL_DESTINATION cmake/original ) install(FILES "${CMAKE_BINARY_DIR}/cmake/originalConfig.cmake" DESTINATION cmake/original)以上的命令用于配置库在安装后的配置信息,使得cmake可以通过
find_package命令找到original的库文件内容。
项目文件夹下的cmake/originalConfig.cmake.in的内容如下:# cmake/originalConfig.cmake.in @PACKAGE_INIT@ set(ORIGINAL_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/install/include") if(WIN32) set(ORIGINAL_LIBRARIES "${CMAKE_SOURCE_DIR}/install/lib/original.lib") else() set(ORIGINAL_LIBRARIES "${CMAKE_SOURCE_DIR}/install/lib/liboriginal.a") endif() include_directories(${ORIGINAL_INCLUDE_DIRS}) if (ORIGINAL_LIBRARIES) link_libraries(${ORIGINAL_LIBRARIES}) endif()这个文件是cmake模版文件,在安装时会生成同名的
.cmake后缀文件,此处配置安装路径的头文件(include)路径和链接文件路径(.a和.lib)。# test cases add_subdirectory(test/legacy) add_subdirectory(test/unit_test)此处是添加子文件夹,用于引用这些目录下的
CmakeLists.txt文件。通过拆分的方式避免所有内容都写在同一个配置文件中,有利于cmake配置的维护和管理。使用GTest+Cmake进行单元测试
Google Test(GTest)是谷歌开发的测试框架,这里我们利用这个框架来进行单元测试。
在test/unit_test文件夹下的结构:

随着模块的增多,还会添加对于更多模块的测试,文件夹格式:test_<module_name>.
cmake配置文件:# test/unit_test/CMakeLists.txt include(FetchContent) FetchContent_Declare( GTest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.11.0 ) FetchContent_MakeAvailable(GTest) add_subdirectory(test_core)此处通过cmake的
FetchContent模块远程拉取GTest模块的内容。
每个add_subdirectory命令用于引入每个测试模块文件夹。
test_core下的结构:

# test/unit_test/test_core/CMakeLists.txt file(GLOB CORE_TESTS "test_*.cpp") add_executable(core_tests ${CORE_TESTS}) target_link_libraries(core_tests original) target_link_libraries(core_tests GTest::gtest GTest::gmock GTest::gmock_main GTest::gtest_main) add_test(NAME CoreTests COMMAND core_tests)此处配置文件首先收集目录下所有的测试文件(均以
.h后缀结尾),统一通过一条add_executable添加为可执行文件,避免了逐一添加的麻烦。target_link_libraries链接了被测试的库(也就是我们的项目Original)和测试框架GTest,通过add_test将该测试模块下的所有测试文件统一注册。由于测试文件内容较多,此处选择其中一个文件进行展示:
#include <gtest/gtest.h> #include <stdexcept> #include "error.h" using namespace original; TEST(ErrorTest, OutOfBoundErrorTest) { outOfBoundError e; EXPECT_STREQ(e.what(), "Out of the bound of the object."); } TEST(ErrorTest, ValueErrorTest) { valueError e; EXPECT_STREQ(e.what(), "Wrong value given."); } TEST(ErrorTest, NullPointerErrorTest) { nullPointerError e; EXPECT_STREQ(e.what(), "Attempting to access null pointer."); } TEST(ErrorTest, UnSupportedMethodErrorTest) { unSupportedMethodError e; EXPECT_STREQ(e.what(), "Unsupported Method for class."); } TEST(ErrorTest, NoElementErrorTest) { noElementError e; EXPECT_STREQ(e.what(), "No such element."); } void validCallback(int a, double b) { std::cout << "a + b = " << a + b << std::endl; } int invalidReturnTypeCallback(int a, double b) { return a + static_cast<int>(b); } void invalidArgumentCallback(int a) { std::cout << a << std::endl; } class sampleClass{}; TEST(CallBackCheckerTest, ValidCallbackTest) { EXPECT_NO_THROW((original::callBackChecker::check<decltype(validCallback), void, int, double>())); } TEST(CallBackCheckerTest, InvalidCallbackTest) { EXPECT_THROW((original::callBackChecker::check<decltype(invalidReturnTypeCallback), float, int, double>()), CallbackReturnTypeError); } TEST(CallBackCheckerTest, InvalidArgumentCallbackTest1) { EXPECT_THROW((original::callBackChecker::check<decltype(invalidArgumentCallback), void, int, int>()), CallbackSignatureError); } TEST(CallBackCheckerTest, InvalidArgumentCallbackTest2) { EXPECT_THROW((original::callBackChecker::check<decltype(invalidArgumentCallback), void, sampleClass>()), CallbackSignatureError); } TEST(CallBackCheckerTest, EmptyCallbackTest) { EXPECT_NO_THROW((original::callBackChecker::check<std::function<void()>, void>())); }每个
TEST宏代码块就是一个测试单元,EXPECT_STREQ断言宏用于比较两个字符串是否相等,EXPECT_NO_THROW断言宏用于断定测试代码是否不会抛出异常(实际未抛出通过测试,实际抛出则测试失败),EXPECT_THROW断言宏用于断定测试代码是否会抛出异常(实际抛出通过测试,实际未抛出则测试失败)。总结
通过给自己的项目的相关现代化改造,使得项目变得更好被维护。他人的学习和使用成本也会降低,自己也学会了很多,受益匪浅。
欢迎各位在评论区的讨论~
-
项目中智能指针多态性丢失的问题已完成。
最终解决方案是,通过cloneable接口的clone方法,实现具体类对象的动态创建:
https://github.com/FrozenLemonTee/original/commit/fe14776ccc411790084dcd4ea1a002d3ee22eaa7
https://github.com/FrozenLemonTee/original/commit/cdd94d92c29c09bb58ca4d6f4b9eadb8272e7e27 -
项目中智能指针多态性丢失的问题可以具体说一下怎么做吗
-
项目中智能指针多态性丢失的问题相关测试的例子在项目的test/test2.cpp中,最后几行就是针对该问题的测试
-
项目中智能指针多态性丢失的问题项目地址:https://github.com/FrozenLemonTee/original
是一个仿STL的工具库项目。问题源自于
transform和transformStream两个类的设计上。transform是一个接口,通过继承这个类并且实现apply方法,可以直接以仿函数的方式调用这个类,将定义好的变换施加给项目中继承了iterable接口的可迭代对象中存放的所有元素。transformStream是一个中间的临时类,同时和transform一样也是可调用类,用于对若干个传入函数的transform所构成的复合调用链进行管理,在自己被调用时按照调用链顺序依次调用相关的transform以达到复合调用的目的。复合调用链用+操作符进行连接,通过重载+操作符自动生成transformStream,并将调用链中的transform加入生成的transformStream的管理。在测试中发现,传入函数的复合调用链的每个
transform派生类对象在被调用时,调用的是基类的apply方法,而基类定义的是空操作,也就导致整个调用链被调用时不施加任何变换。经过检查是transformStream的pushEnd方法存在问题:template<typename TYPE> void original::transformStream<TYPE>::pushEnd(const transform<TYPE>& t) { this->stream.pushEnd(std::make_shared<std::decay_t<decltype(t)>>(t)); }当以智能指针将传入的
transform包装时,智能指针的参数无法获得传入的对象t正确的派生类型,导致对象t以基类的形式被存储。我尝试了很多方案想要解决这个问题,一开始怀疑是自定义的容器
chain的问题,于是换成std::vector,或者考虑以std::function的方式存储变换,但是都无效,想请教一下如何设计可以避免这个问题?