Non-aggregate initialization下,如何保留类不变量创建AutoDocs数组?
问题描述
需要实现一个AutoDocs类,用于存储车牌号码和驾驶员全名信息,该类需满足以下不变量:
- 车牌为6位字符,符合
x123xx格式(x为字母,123为数字) - 驾驶员全名合法(非空且不含数字)
要将该类实例存入大小为N的数组,但当前代码中AutoDocs删除了默认构造函数,数组默认初始化会报错;若添加默认构造函数,又会破坏类的不变量(创建出不合法的实例),请问如何解决?
现有代码
#include <string> #include <string_view> #include <cassert> #include <algorithm> #include <cctype> #include <cstdint> class FullName { public: FullName() = delete; // 类不变量:m_name和m_surname非空且不含数字 FullName(std::string_view name, std::string_view surname) : m_name{ (assert(isvalid(name) && "INVALID NAME"), name) } , m_surname{ (assert(isvalid(surname) && "INVALID SURNAME"), surname) } {} auto setName(std::string_view name) { if (isvalid(name)) m_name = name; } auto setSurname(std::string_view surname) { if (isvalid(surname)) m_surname = surname; } bool isvalid(std::string_view str) const; private: std::string m_name{}; std::string m_surname{}; }; bool FullName::isvalid(std::string_view str) const { if (str.empty()) return false; if (std::any_of(std::begin(str), std::end(str), [=](char ch) -> bool { return isdigit(ch); })) return false; return true; } class StateNumber { public: StateNumber() = delete; // 类不变量:字符串长度为6且符合x123xx格式 StateNumber(std::string_view stateNumber) : m_letters{ ( assert(isvalid(stateNumber) && "INVALID STATE_NUMBER"), (std::string(1, stateNumber[0]) + stateNumber[4] + stateNumber[5]) ) } , m_numbers{ static_cast<std::uint16_t>(std::stoi(std::string(1, stateNumber[1]) + stateNumber[2] + stateNumber[3])) } {} bool isvalid(std::string_view s) const { return s.length() == 6 && isalpha(s[0]) && isdigit(s[1]) && isdigit(s[2]) && isdigit(s[3]) && isalpha(s[4]) && isalpha(s[5]); } auto setNumbers(std::uint16_t numbers) { if (numbers <= 999) m_numbers = numbers; } auto setLetters(std::string_view letters); private: std::string m_letters{}; std::uint16_t m_numbers{}; }; auto StateNumber::setLetters(std::string_view letters) { if (std::any_of(std::begin(letters), std::end(letters), [=](char ch) -> bool { return isdigit(ch); })) return; if (letters.length() != 3) return; std::string letters_copy{ letters }; std::transform(std::begin(letters_copy), std::end(letters_copy), std::begin(letters_copy), [&](char ch){ return std::tolower(ch); } ); m_letters = letters_copy; } class AutoDocs { public: AutoDocs() = delete; AutoDocs(const FullName& fullName, const StateNumber& stateNumber) : m_fullName{ fullName }, m_stateNumber{ stateNumber } {} private: FullName m_fullName{}; StateNumber m_stateNumber{}; }; int main() { std::uint32_t N{}; AutoDocs array[N]; // 此处初始化失败,因为默认构造已删除 return 0; }
解决方案
1. 用std::vector替代静态数组(推荐)
这是最简洁的方案,std::vector支持动态添加合法实例,无需默认构造函数,完全保证类不变量:
#include <vector> int main() { std::uint32_t N = 3; // 假设N为已知有效值 std::vector<AutoDocs> docs; docs.reserve(N); // 预分配内存,避免多次扩容 // 创建合法的基础实例 FullName validName("John", "Doe"); StateNumber validPlate("A123BC"); // 批量添加实例(也可以根据不同数据创建不同实例) for (std::uint32_t i = 0; i < N; ++i) { docs.emplace_back(validName, validPlate); } // 使用vector中的AutoDocs实例... return 0; }
2. 用std::optional包装数组元素
如果必须用数组,可以用std::optional<AutoDocs>作为数组元素,默认初始化为空状态,之后再赋值合法实例:
#include <optional> int main() { std::uint32_t N = 3; std::optional<AutoDocs> array[N]; // 默认都是std::nullopt,无非法实例 // 初始化合法实例 FullName validName("Jane", "Smith"); StateNumber validPlate("X456YZ"); array[0] = AutoDocs(validName, validPlate); array[1] = AutoDocs(FullName("Alice", "Johnson"), StateNumber("B789CD")); // 使用时需检查是否已初始化 if (array[0]) { // 操作array[0].value()... } return 0; }
3. 手动内存管理 + 放置new
如果需要严格的数组内存布局且不想用容器,可以手动分配内存,用放置new在原始内存中构造合法的AutoDocs实例,避免调用默认构造:
#include <memory> int main() { std::uint32_t N = 3; // 分配原始内存,不调用构造函数 auto* rawMem = static_cast<AutoDocs*>(std::malloc(N * sizeof(AutoDocs))); if (!rawMem) { return 1; // 处理内存分配失败 } FullName validName("Bob", "Brown"); StateNumber validPlate("C012DE"); try { // 逐个构造合法实例 for (std::uint32_t i = 0; i < N; ++i) { new (rawMem + i) AutoDocs(validName, validPlate); } // 使用数组:rawMem[i]就是合法的AutoDocs实例... // 手动销毁每个实例 for (std::uint32_t i = 0; i < N; ++i) { rawMem[i].~AutoDocs(); } } catch (...) { // 异常时销毁已构造的实例,避免内存泄漏 for (std::uint32_t i = 0; i < N; ++i) { rawMem[i].~AutoDocs(); } std::free(rawMem); throw; } std::free(rawMem); return 0; }
方案对比
std::vector:最易用,自动管理内存,完全符合类不变量,优先推荐。std::optional:保留数组结构,直观易懂,适合需要固定大小预留位置的场景。- 放置new:底层实现,适合对内存布局或性能有特殊要求的场景,但需要手动管理内存,容易出错。
内容的提问来源于stack exchange,提问作者swecpp
相关产品推荐
相关产品推荐

