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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 11:29:51