GCC8中std::variant与移动构造函数Bug:多编译器兼容异常
GCC 8.0下std::variant与拷贝构造函数的兼容问题排查与修复
嘿,这个问题我熟!GCC 8.0在std::variant的拷贝构造处理上确实跟旧版本有不小的差异,尤其是结合std::unique_ptr这种不可拷贝类型的时候,我帮你捋捋问题根源和解决办法。
首先先把你没写完的代码补全(大概是这样吧?),方便分析:
#include <iostream> #include <variant> #include <memory> #include <type_traits> struct EmptyTree{}; struct Node { using node = std::unique_ptr<Node>; using Tree = std::variant<node, EmptyTree>; int value; Tree left; Tree right; Node(int v) : value(v){} template<typename left, typename right> Node(int v, left &&l, right &&r) : value(v), left(std::forward<left>(l)), right(std::forward<right>(r)) {} }; // 触发问题的测试代码示例 int main() { Node n1(1); Node n2(2, n1.left, n1.right); // 这里尝试拷贝n1的left/right,会在GCC8.0报错 return 0; }
问题根源
GCC 8.0开始严格遵循C++17标准中关于std::variant拷贝构造的要求:当variant的任意一个备选类型不可拷贝时,variant的拷贝构造函数会被自动删除。而你的Tree类型是std::variant<std::unique_ptr<Node>, EmptyTree>,其中std::unique_ptr是不可拷贝的类型,所以Tree的拷贝构造函数被删除了。
旧版本的GCC(7.2)、Clang 7和MSVC在这方面的检查更宽松,可能通过非标准的实现绕过了这个限制,所以你的代码在这些编译器上能正常编译,但到了GCC 8.0就触发了合规性错误。
解决方案
根据你的业务需求,有两种可行的修复方式:
方案一:改用移动语义(推荐,符合unique_ptr的设计初衷)
既然std::unique_ptr本身就是用来独占所有权的,我们干脆显式禁用Node的拷贝构造/赋值,改用移动语义来转移所有权:
struct Node { using node = std::unique_ptr<Node>; using Tree = std::variant<node, EmptyTree>; int value; Tree left; Tree right; Node(int v) : value(v){} template<typename left, typename right> Node(int v, left &&l, right &&r) : value(v), left(std::forward<left>(l)), right(std::forward<right>(r)) {} // 显式禁用拷贝 Node(const Node&) = delete; Node& operator=(const Node&) = delete; // 自动生成移动构造/赋值 Node(Node&&) = default; Node& operator=(Node&&) = default; };
使用的时候用std::move转移所有权:
int main() { Node n1(1); Node n2(2, std::move(n1.left), std::move(n1.right)); return 0; }
方案二:换成可拷贝的智能指针(如果需要拷贝语义)
如果你的业务场景确实需要Tree支持拷贝,把std::unique_ptr换成std::shared_ptr即可——shared_ptr是可拷贝的,这样std::variant的拷贝构造函数就能正常生成:
struct Node { using node = std::shared_ptr<Node>; using Tree = std::variant<node, EmptyTree>; int value; Tree left; Tree right; Node(int v) : value(v){} template<typename left, typename right> Node(int v, left &&l, right &&r) : value(v), left(std::forward<left>(l)), right(std::forward<right>(r)) {} // 默认的拷贝构造/赋值可以正常工作,无需额外修改 };
内容的提问来源于stack exchange,提问作者Antoine Morrier
相关产品推荐
相关产品推荐

