C++ operator<<输出异常修复及std::variant替换union方案
问题根因
代码输出异常由4个核心问题导致:
- 无参构造函数未初始化
length成员,对象创建时length为内存残留的随机垃圾值;同时无参构造仅为selling分配1字节堆空间,后续直接strcpy长字符串触发堆缓冲区溢出,属于未定义行为,直接导致首行乱码。 - 手动调用
strcpy给selling赋值后,未同步更新length成员值,重载的operator<<调用os.write(s.selling, s.length)时,会按照错误的长度读取内存,将不属于字符串的垃圾数据一并输出。 - 原带参构造函数中存在多余的
std::cout << s << "\n";语句,会在对象构造时自动打印字符串,后续循环的printf也会再次输出商品名,造成重复打印。 - 类使用裸指针管理堆内存,但未实现拷贝构造、拷贝赋值函数,默认生成的浅拷贝逻辑会导致重复释放内存的崩溃风险。
修复后完整代码(已集成std::variant替换自定义联合体)
#include <iostream> #include <variant> #include <cstring> #include <cstdio> using namespace std; class Product { public: char* selling; unsigned length; float unitPrice; variant<int, float> amount; // 用类型安全的variant替换原自定义联合体 // 无参构造函数 Product(); // 带参构造函数 Product(const char* s, float unitPrice, variant<int, float> amt); // 拷贝构造函数(遵循Rule of Three,避免浅拷贝问题) Product(const Product& other); // 拷贝赋值运算符 Product& operator=(const Product& other); ~Product() { delete[] selling; } unsigned len() const; Product& operator+= (const Product& s); char& operator[](size_t idx); char operator[](size_t idx) const; friend std::ostream& operator<<(std::ostream& os, const Product& s) { os.write(s.selling, s.length); return os; } int size() { return length; } // 封装名称设置方法,避免手动strcpy导致的溢出、length不同步问题 void setName(const char* s) { if (s == nullptr) { delete[] selling; selling = new char[1]{'\0'}; length = 0; return; } delete[] selling; length = strlen(s); selling = new char[length + 1]; strcpy(selling, s); } }; // 无参构造实现,初始化所有成员 Product::Product() : selling{new char[1] {'\0'}}, length{0}, unitPrice{0.00f}, amount{0} {} // 带参构造实现,删除原多余的cout打印逻辑 Product::Product(const char* s, float unitPrice, variant<int, float> amt) : unitPrice(unitPrice), amount(amt) { if (s == nullptr) { selling = new char[1]{'\0'}; length = 0; this->unitPrice = 0.00f; } else { length = strlen(s); selling = new char[length + 1]; strcpy(selling, s); } } // 拷贝构造实现 Product::Product(const Product& other) { length = other.length; selling = new char[length + 1]; strcpy(selling, other.selling); unitPrice = other.unitPrice; amount = other.amount; } // 拷贝赋值实现 Product& Product::operator=(const Product& other) { if (this == &other) return *this; delete[] selling; length = other.length; selling = new char[length + 1]; strcpy(selling, other.selling); unitPrice = other.unitPrice; amount = other.amount; return *this; } unsigned Product::len() const { return length; } Product& Product::operator+=(const Product& s) { unsigned newLen = length + s.length; char* str = new char[newLen + 1]; for (unsigned j = 0; j < length; j++) str[j] = selling[j]; for (unsigned i = 0; i < s.length; i++) str[length + i] = s.selling[i]; str[newLen] = '\0'; delete[] selling; length = newLen; selling = str; return *this; } Product operator+(const Product& lhs, const Product& rhs) { Product rv(lhs); rv += rhs; return rv; } char& Product::operator[](size_t idx) { return selling[idx]; } char Product::operator[](size_t idx) const { return selling[idx]; } // variant访问器,自动匹配库存类型打印对应信息 struct InventoryPrinter { void operator()(int units) const { printf("We have %d units for sale\n", units); } void operator()(float kgs) const { printf("We have %f kgs for sale\n", kgs); } }; int main() { int numItems = 2; int i; Product dieselMotorBike; Product apples; dieselMotorBike.setName("A Diesel Motor Cycle"); dieselMotorBike.unitPrice = 5488.00f; dieselMotorBike.amount = 4; // variant自动识别存储int类型 cout << dieselMotorBike << "\n"; apples.setName("Granny duBois"); apples.unitPrice = 0.78f; apples.amount = 0.5f; // variant自动识别存储float类型 cout << apples << "\n"; Product* myEbayStore[2]; myEbayStore[0] = &dieselMotorBike; myEbayStore[1] = &apples; for (i = 0; i < numItems; i++) { printf("\n%s\n", myEbayStore[i]->selling); // 用visit安全访问variant值,不需要手动switch判断类型 visit(InventoryPrinter{}, myEbayStore[i]->amount); } return 0; }
std::variant替换说明
相比自定义联合体,std::variant是类型安全的,不需要额外维护unitType这类标记字段记录当前存储的类型:
- 定义
variant<int, float> amount作为库存成员后,赋值时会自动匹配存储类型:传入整数自动存int,传入浮点数自动存float。 - 访问值时使用
std::visit,传入重载了对应类型operator()的访问器即可,编译器会在编译期检查是否覆盖了所有可能的类型,不会出现自定义联合体类型访问不匹配的未定义行为。 - 如果需要判断当前存储的类型,可以调用
amount.index(),返回0代表存储的是第一个类型int,返回1代表存储的是第二个类型float;也可以用holds_alternative<int>(amount)直接判断当前是否存储int值。
内容的提问来源于stack exchange,提问作者Hector Ta
相关产品推荐
相关产品推荐

