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

如何为使用std::source_location的构造函数禁用复制消除?

问题背景

我正在为产品中广泛使用的模板类添加插桩逻辑,当前开发环境为VS 2019 (16.10.4),编译选项为/std:c++17。std::source_location特性非常适配我要解决的任务,但我的编译器不支持std::source_location和std::experimental::source_location,因此我参考相关资料自行实现了该功能。我的目标是在特殊构建版本中修改类的构造函数和成员后运行测试,修改类本身不改变其用法,其余代码无需调整。

上述实现大部分情况下编译运行正常,但遇到了copy elision(复制消除)问题,几乎让使用std::source_location的目的落空。我希望获取的source_location是调用方定义变量的位置,而非实际创建对象的位置。

这个问题在gcc -std=c++20环境下开启或关闭-fno-elide-constructors选项时也可复现。

MyClass定义:

class MyClass
{
private:
    int m_a = 0;
    std::source_location m_location;

public:
    MyClass(std::source_location location = std::source_location::current())
    : m_location(location)
    {
    }
    MyClass(const MyClass &other, std::source_location location = std::source_location::current())
    : m_a(other.m_a)
    , m_location(location)
    {
    }
    MyClass(MyClass &&other, std::source_location location = std::source_location::current())
    : m_a(other.m_a)
    , m_location(location)
    {
    }
};

调用示例:

MyClass getCopy1()
{
    MyClass ret;
    return ret;
}

MyClass getCopy2()
{
    return MyClass();
}

int main()
{
    MyClass o1;
    MyClass o2(o1);
    MyClass o3(getCopy1());
    MyClass o4(getCopy2());

    std::cout << "o1: " << o1.getLocationInfo() << std::endl;
    std::cout << "o2: " << o2.getLocationInfo() << std::endl;
    std::cout << "o3: " << o3.getLocationInfo() << std::endl;
    std::cout << "o4: " << o4.getLocationInfo() << std::endl;

    return 0;
}

实际输出:

o1: /app/example.cpp(56:13) int main()
o2: /app/example.cpp(57:18) int main()
o3: /app/example.cpp(46:12) MyClass getCopy1()
o4: /app/example.cpp(51:20) MyClass getCopy2()

预期输出:

o1: /app/example.cpp(56:13) int main()
o2: /app/example.cpp(57:18) int main()
o3: /app/example.cpp(58:26) int main()
o4: /app/example.cpp(59:26) int main()

现有可行但不实用的解决方案:

  • 方案1(显式传参):
MyClass o3(getCopy1(), std::source_location::current());
MyClass o4(getCopy2(), std::source_location::current());
  • 方案2(使用std::move):
MyClass o3(std::move(getCopy1()));
MyClass o4(std::move(getCopy2()));

上述两种方案都能得到预期结果,但需要修改大量上层调用代码,违背了使用std::source_location降低改造成本的初衷。我可以接受不破坏原有调用方式的MyClass.h/cpp修改,或者编译器选项调整,该插桩版本仅用于测试,不用于生产,产品当前基于VS 2019 (16.10.4)、/std:c++17编译,请问有什么合适的解决方案?


解决方案

方案1:编译选项调整(零代码修改)

针对VS2019环境,直接在测试构建的编译参数中添加/Zc:copyElision-即可。该参数会禁用C++17标准要求的强制复制消除特性,你的原有MyClass实现不需要做任何修改,即可得到预期的调用侧位置信息。该方案完全符合测试版本的使用场景,不需要修改任何业务代码,仅需要单独调整测试构建的编译配置即可,不要在生产构建中开启该选项。

方案2:隐式转换代理实现(零调用侧修改)

如果不希望修改编译选项,可以通过轻量代理类利用隐式转换的时机捕获调用侧位置,仅需要调整MyClass的头文件定义,上层调用代码完全不需要改动:

class MyClass;

// 轻量代理类,用于捕获转换位置
class MyClassProxy {
private:
    MyClass&& tmp_obj;
    std::source_location loc;
    friend class MyClass;
public:
    MyClassProxy(MyClass&& obj, std::source_location l = std::source_location::current())
        : tmp_obj(std::move(obj)), loc(l) {}
};

class MyClass
{
private:
    int m_a = 0;
    std::source_location m_location;
    // 原拷贝/移动构造设为私有,禁止外部直接调用
    MyClass(const MyClass &other, std::source_location location = std::source_location::current())
    : m_a(other.m_a)
    , m_location(location)
    {
    }
    MyClass(MyClass &&other, std::source_location location = std::source_location::current())
    : m_a(other.m_a)
    , m_location(location)
    {
    }
    friend class MyClassProxy;
public:
    MyClass(std::source_location location = std::source_location::current())
    : m_location(location)
    {
    }
    // 新增代理构造函数,使用调用侧捕获的位置
    MyClass(MyClassProxy proxy) 
        : MyClass(std::move(proxy.tmp_obj), proxy.loc) {}

    // 原有getLocationInfo等逻辑保持不变
    std::string getLocationInfo() const {
        // 原有实现不变
    }
};

该方案同时兼容GCC和VS编译器,不需要调整任何上层调用代码即可得到预期输出。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:48:00