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

如何根据构造器输入初始化std::variant类成员变量

C++17中std::variant根据运行时参数初始化的正确实现

问题描述

我有一个BASE类,其中包含类型为std::variant<A, B>的成员变量myVar,该变量可存储A类或B类对象。希望在运行时根据构造器的输入,调用A或B的带参构造函数来初始化该变量。

尝试过两种方式:

  • 第一种:在构造函数内通过条件判断赋值,但赋值后临时对象的析构函数会被调用,导致myVar存储垃圾数据;
  • 第二种:尝试用初始化列表,但条件表达式的写法无法通过编译。

附尝试代码:

#include <iostream>
#include <string>
#include <variant>

class A {
private:
    char* arr;
public:
    A() = default;
    A(int n) {
        arr = new char[3];
        arr[0] = arr[1] = arr[2] = 'A';
        std::cout << "Constructor A called with " + std::to_string(n);
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
    }
    ~A() {
        std::cout << "Destructor A called";
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
        delete[] arr;
    }
};
class B {
private:
    char* arr;
public:
    B() = default;
    B(int n) {
        arr = new char[3];
        arr[0] = arr[1] = arr[2] = 'B';
        std::cout << "Constructor B called with " + std::to_string(n);
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
    }
    ~B() {
        std::cout << "Destructor B called ";
        std::cout << " [" << arr[0]  << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
        delete[] arr;
    }
};


class BASE {
private: 
     std::variant<A, B> myVar;

public:
     BASE() = delete;
     BASE(int i) {
         if (i == 0) {
             std::cout << "Initializing A" << std::endl;
             myVar = A(i);
         } else {
             std::cout << "Initializing B" << std::endl;
             myVar = B(i);
         }
     }
};

int main() {
    BASE test1(0);
}

问题根源

第一种方法出现垃圾数据的核心原因是:A和B类没有定义正确的拷贝/移动构造函数,编译器生成的默认版本是浅拷贝。当执行myVar = A(i)时,临时A对象的arr指针会被拷贝到variant存储的A对象中,临时对象析构时会释放arr指向的内存,导致variant里的A对象持有野指针,访问时出现非法数据。

解决方案

方案1:修复A和B的拷贝/移动语义

给A和B添加移动构造函数(或拷贝构造函数),实现资源所有权转移或深拷贝,避免野指针问题:

#include <iostream>
#include <string>
#include <variant>
#include <utility>

class A {
private:
    char* arr;
public:
    A() = default;
    A(int n) {
        arr = new char[3];
        arr[0] = arr[1] = arr[2] = 'A';
        std::cout << "Constructor A called with " + std::to_string(n);
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
    }
    // 移动构造函数:转移资源所有权
    A(A&& other) noexcept : arr(other.arr) {
        other.arr = nullptr; // 避免原对象析构时释放内存
        std::cout << "Move Constructor A called" << std::endl;
    }
    // 禁用拷贝构造(若不需要拷贝功能)
    A(const A&) = delete;
    A& operator=(const A&) = delete;
    // 移动赋值运算符
    A& operator=(A&& other) noexcept {
        if (this != &other) {
            delete[] arr;
            arr = other.arr;
            other.arr = nullptr;
        }
        std::cout << "Move Assignment A called" << std::endl;
        return *this;
    }
    ~A() {
        if (arr) {
            std::cout << "Destructor A called";
            std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
            delete[] arr;
        } else {
            std::cout << "Destructor A called (nullptr)" << std::endl;
        }
    }
};

class B {
private:
    char* arr;
public:
    B() = default;
    B(int n) {
        arr = new char[3];
        arr[0] = arr[1] = arr[2] = 'B';
        std::cout << "Constructor B called with " + std::to_string(n);
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
    }
    // 移动构造函数
    B(B&& other) noexcept : arr(other.arr) {
        other.arr = nullptr;
        std::cout << "Move Constructor B called" << std::endl;
    }
    // 禁用拷贝构造
    B(const B&) = delete;
    B& operator=(const B&) = delete;
    // 移动赋值运算符
    B& operator=(B&& other) noexcept {
        if (this != &other) {
            delete[] arr;
            arr = other.arr;
            other.arr = nullptr;
        }
        std::cout << "Move Assignment B called" << std::endl;
        return *this;
    }
    ~B() {
        if (arr) {
            std::cout << "Destructor B called ";
            std::cout << " [" << arr[0]  << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
            delete[] arr;
        } else {
            std::cout << "Destructor B called (nullptr)" << std::endl;
        }
    }
};

class BASE {
private: 
     std::variant<A, B> myVar;

public:
     BASE() = delete;
     BASE(int i) {
         if (i == 0) {
             std::cout << "Initializing A" << std::endl;
             myVar = A(i); // 调用移动赋值,转移资源所有权
         } else {
             std::cout << "Initializing B" << std::endl;
             myVar = B(i);
         }
     }
};

int main() {
    BASE test1(0);
}

方案2:在初始化列表中直接构造variant(避免临时对象)

如果不想修改A和B的结构,可以通过辅助函数在初始化列表中直接构造std::variant,从根源上避免临时对象析构的问题:

#include <iostream>
#include <string>
#include <variant>

class A {
private:
    char* arr;
public:
    A() = default;
    A(int n) {
        arr = new char[3];
        arr[0] = arr[1] = arr[2] = 'A';
        std::cout << "Constructor A called with " + std::to_string(n);
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
    }
    ~A() {
        std::cout << "Destructor A called";
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
        delete[] arr;
    }
};
class B {
private:
    char* arr;
public:
    B() = default;
    B(int n) {
        arr = new char[3];
        arr[0] = arr[1] = arr[2] = 'B';
        std::cout << "Constructor B called with " + std::to_string(n);
        std::cout << " [" << arr[0] << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
    }
    ~B() {
        std::cout << "Destructor B called ";
        std::cout << " [" << arr[0]  << "," << arr[1] << "," << arr[2] << "]" << std::endl; 
        delete[] arr;
    }
};

class BASE {
private: 
     std::variant<A, B> myVar;

    // 辅助函数:根据参数直接构造对应类型的variant
    static std::variant<A, B> createVar(int i) {
        if (i == 0) {
            return std::variant<A, B>(std::in_place_type<A>, i);
        } else {
            return std::variant<A, B>(std::in_place_type<B>, i);
        }
    }

public:
     BASE() = delete;
     // 初始化列表中直接构造myVar,无临时对象
     BASE(int i) : myVar(createVar(i)) {
         std::cout << "Initializing " << (i == 0 ? "A" : "B") << std::endl;
     }
};

int main() {
    BASE test1(0);
}

这个方案通过std::in_place_type直接在variant内部构造A或B对象,不需要创建临时对象,彻底避免了野指针问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 15:16:03