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

如何强制Base类使用者新增枚举ID并通过SubclassFactory创建实例?

Great question! Let's tackle both requirements with compile-time enforcement first (since that's the optimal solution) and complement with unit tests for extra safety.

1. Force Instance Creation Only via SubclassFactory

The cleanest compile-time way to prevent direct instantiation of subclasses is using a private token pattern. This ensures only the factory can call the constructor of Base (and thus subclasses):

#include <memory>
#include <stdexcept>

// Forward declare factory to make it a friend
class SubclassFactory;

class Base {
private:
    // Private token only accessible to friends
    struct FactoryToken {
        explicit FactoryToken() = default;
        FactoryToken(const FactoryToken&) = delete;
        FactoryToken& operator=(const FactoryToken&) = delete;
    };

    // Grant factory exclusive access to the token
    friend class SubclassFactory;

protected:
    // Protected constructor requires the token
    explicit Base(FactoryToken) {}

public:
    virtual ~Base() = default;
    // Add your base class methods here
};

// Example subclass
class SubclassA : public Base {
protected:
    // Subclass constructor also requires the token (called by factory)
    explicit SubclassA(FactoryToken token) : Base(std::move(token)) {}

    friend class SubclassFactory;
};

enum class SubclassId {
    A,
    // Add new enum entries here when creating subclasses
};

class SubclassFactory {
public:
    static std::unique_ptr<Base> create(SubclassId id) {
        switch (id) {
            case SubclassId::A:
                return std::make_unique<SubclassA>(Base::FactoryToken{});
            default:
                throw std::invalid_argument("Unknown SubclassId");
        }
    }
};

Why this works:

  • Any attempt to instantiate SubclassA directly (e.g., new SubclassA()) will fail at compile time: the constructor is protected, and you can't create FactoryToken (it's private to Base).
  • Only SubclassFactory can create the token and pass it to the constructor, ensuring all instances go through the factory.

2. Force Adding SubclassId Entry for New Subclasses

To enforce this at compile time, we'll use CRTP (Curiously Recurring Template Pattern) and compile-time assertions with a type-to-enum trait. Here's how:

Step 1: Add CRTP to Base and Type-to-Enum Trait

Modify the Base class to use CRTP, and create a trait that maps each subclass to its corresponding SubclassId:

#include <array>
#include <tuple>
#include <type_traits>

// Enum definition
enum class SubclassId {
    A,
    B // New entry for SubclassB
};

// Trait to map subclass types to their SubclassId
template<typename Derived>
struct SubclassToId;

// Specialization for SubclassA
template<>
struct SubclassToId<SubclassA> {
    static constexpr SubclassId value = SubclassId::A;
};

// CRTP Base class
template<typename Derived>
class Base {
private:
    struct FactoryToken { /* same as before */ };
    friend class SubclassFactory;

protected:
    explicit Base(FactoryToken) {}

public:
    virtual ~Base() = default;

    // Compile-time check: Derived must have a valid SubclassToId specialization
    static_assert(
        std::is_same_v<decltype(SubclassToId<Derived>::value), SubclassId>,
        "ERROR: You must specialize SubclassToId for this subclass with a valid SubclassId"
    );

    // Get the subclass ID from the trait
    static constexpr SubclassId getId() {
        return SubclassToId<Derived>::value;
    }
};

// Update SubclassA to inherit from CRTP Base
class SubclassA : public Base<SubclassA> {
protected:
    explicit SubclassA(FactoryToken token) : Base(std::move(token)) {}
    friend class SubclassFactory;
};

// New subclass example
class SubclassB : public Base<SubclassB> {
protected:
    explicit SubclassB(FactoryToken token) : Base(std::move(token)) {}
    friend class SubclassFactory;
};

// Specialize SubclassToId for SubclassB (required!)
template<>
struct SubclassToId<SubclassB> {
    static constexpr SubclassId value = SubclassId::B;
};

Step 2: Compile-Time Check for Full Enum Coverage

To ensure every SubclassId has a corresponding subclass (and vice versa), add these compile-time assertions:

// Compile-time list of all SubclassId values
constexpr std::array<SubclassId, 2> ALL_SUBCLASS_IDS = {SubclassId::A, SubclassId::B};

// Compile-time tuple of all subclass types
using ALL_SUBCLASSES = std::tuple<SubclassA, SubclassB>;

// Helper to get all subclass IDs as a compile-time array
template<typename Tuple>
constexpr auto get_subclass_ids() {
    return []<std::size_t... I>(std::index_sequence<I...>) {
        return std::array<SubclassId, sizeof...(I)>{
            SubclassToId<std::tuple_element_t<I, Tuple>>::value...
        };
    }(std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}

constexpr auto SUBCLASS_IDS_FROM_TYPES = get_subclass_ids<ALL_SUBCLASSES>();

// Check 1: Every SubclassId has a corresponding subclass
static_assert([]{
    for (auto enum_id : ALL_SUBCLASS_IDS) {
        bool found = false;
        for (auto subclass_id : SUBCLASS_IDS_FROM_TYPES) {
            if (enum_id == subclass_id) { found = true; break; }
        }
        if (!found) return false;
    }
    return true;
}(), "ERROR: All SubclassId entries must have a corresponding subclass");

// Check 2: Every subclass has a unique, valid SubclassId
static_assert([]{
    for (auto subclass_id : SUBCLASS_IDS_FROM_TYPES) {
        bool found = false;
        for (auto enum_id : ALL_SUBCLASS_IDS) {
            if (subclass_id == enum_id) { found = true; break; }
        }
        if (!found) return false;
    }
    // Also check for duplicate IDs
    for (std::size_t i = 0; i < SUBCLASS_IDS_FROM_TYPES.size(); ++i) {
        for (std::size_t j = i+1; j < SUBCLASS_IDS_FROM_TYPES.size(); ++j) {
            if (SUBCLASS_IDS_FROM_TYPES[i] == SUBCLASS_IDS_FROM_TYPES[j]) return false;
        }
    }
    return true;
}(), "ERROR: All subclasses must have a unique, valid SubclassId entry");

Why this works:

  • If you create a new subclass without specializing SubclassToId, the static_assert in Base<Derived> will throw a compile error.
  • If you add a SubclassId entry but forget to add the corresponding subclass to ALL_SUBCLASSES, the first static_assert fails.
  • If you assign a duplicate or invalid SubclassId to a subclass, the second static_assert catches it.

Complementary Unit Test

For extra safety (especially if you're working with a team), add a unit test that verifies every SubclassId can be instantiated via the factory:

#include <gtest/gtest.h>

TEST(SubclassFactoryTest, AllEnumValuesCanBeCreated) {
    // Iterate over all SubclassId values
    for (const auto id : ALL_SUBCLASS_IDS) {
        EXPECT_NO_THROW(SubclassFactory::create(id));
        ASSERT_NE(SubclassFactory::create(id), nullptr);
    }
}

This test will fail if someone adds an enum entry but forgets to update the factory's switch statement.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:45:37