设计具有变体字段的文本类型类,其中可以存储一个或三个对象

Designing a literal-type class with a variant field inside in which one or three objects may be stored

本文关键字:对象 存储 三个 一个 字段 类型 文本      更新时间:2023-10-16

我正在尝试设计一个类 - 为了讨论起见,我们称之为A- 它将满足一组特定的要求:

  1. A必须是文本类型,以允许编译器在编译时通过constexpr构造函数初始化其全局实例(在源代码中创建许多这种类型的全局const对象)。这样做的原因是A对整数使用了简单的编译时加密(主要是 XOR)。稍后在运行时访问相应的int时完成解密。
  2. 它的中央私有字段是一个简单的整数。但是,该类有两个构造函数:A::A(int x)A::A(int x, int y, int z).如果调用第一个版本,则稍后在运行时,每当进行需要使用它的方法调用时,类将在内部使用该单个x。相反,如果使用具有三个参数的第二个版本,则在运行时将决定在调用需要xyz中的哪一个。
  3. A必须是单个类型;第一个构造函数的一个类型,第二个构造函数的另一个类型不令人满意(A经常作为参数传递给函数,所以如果没有这个约束,我需要复制或模板化所有这些函数)。
  4. 所有A对象中的绝大多数都是全局常量,赋值很少发生,如果是这样,它肯定不会在具有三个int的对象和具有一个int的对象之间,反之亦然。

总结一下:A将被实例化为全局const对象。如果我用单个int初始化一个对象,该单个int应该存储在其中(仅此而已)。如果我用三个int初始化它,那么这三个int应该存储在里面。无需担心从三int对象到一int对象的赋值,反之亦然,因为它们都是常量。

到目前为止,我考虑的解决方案如下:

  • A做一个模板;模板参数就是我所说的StorageType。该存储将通过公开该中心int资源的访问器来抽象对该资源的访问。然后,选择要使用的int字段的问题将从A移动到此帮助程序存储类型。这个想法大致由以下代码说明:

    template<typename StorageType>
    class A
    {
    private:
    StorageType storage;
    public:
    constexpr A(int x, int y, int z) :
    storage(x, y, z)
    { }
    constexpr A(int x) :
    storage(x)
    { }
    void doSomething()
    {
    auto theRightInt = storage.getInt();
    // ...
    }
    };
    

    问题:违反约束 3。

  • 和以前一样,但不是在StorageType上模板化A,有一个描述StorageType的通用接口,并在A内部存储一个unique_ptr

    问题:违反约束 1。

  • 将整数存储为联合:

    union
    {
    struct
    {
    int x;
    int y;
    int z;
    } intPack;
    int singleInt;
    };
    

    在此变体中,每个A对象(包括仅使用单个int的对象)都有三个可能int的空间。另一种选择是使用boost::variant而不是过时的union.

    问题:这是浪费 - 见第4点。如果使用boost::variant,则违反约束 1,因为与 C++17 中的std::variant不同,boost::variant不是文本类型。

  • 与其试图在A内部表示中心整数字段的"方差",不如在外部进行:让类始终存储单个int,并创建一个辅助类 - 我们称之为VariantA- 其中包含三个版本的A,每个版本都使用第2点提到的构造函数中xyz的内容进行初始化。VariantA将有一个访问器函数,该函数在运行时决定返回哪个A对象。

    问题:使用起来很乏味,因为每次都必须调用访问器:

    VariantA a1(0, 1, 2);
    VariantA a2(10, 20, 30);
    auto a3 = a1.get() + a2.get();
    someFunctionThatTakesAnA(a1.get());
    // etc
    

问题:对于这个问题,是否有我错过的优雅解决方案,或者我是否必须选择我上面考虑(并拒绝)的解决方案之一? 我的平台是VC++,所以使用C++11/4是可以的,除了一些更深奥的部分,但C++17草案的功能尚不可用。

似乎您可以使用的最好的东西是有条件大小的整数范围:

class A {
std::array<int, 3> data;
bool one;
public:
constexpr A(int x): data{{x, 0, 0}}, one(true) { }
constexpr A(int x, int y, int z): data{{x, y, z}}, one(false) { }
constexpr size_t size() const { return one ? 1 : 3; }
constexpr const int* begin() const { return data.data(); }
constexpr const int* end() const { return data.data() + size(); }
};

我不完全确定你选择哪个元素的逻辑是什么,但是如果你有一个 1 大小的范围,你会得到那个元素,如果你有一个 3 大小的范围,那么你做你需要做的任何事情。


不管具体细节如何,重点是你想要一个有 3 个ints 和一个bool的类型。你需要存储 3int秒,你希望它是文字的,并且你想让它知道它是存储 3 还是 1int。这几乎说明了类型。剩下的唯一事情是确定您希望数据访问的外观,这没有指定。

一个有 2 个成员的类/结构?

指向int的指针和bool(布尔值表示指针指向一个还是三个值)?

#include <iostream>
struct foo
{
bool           single;
int const    * p;
constexpr foo ( int const & x ) : single{true}, p{&x}
{}
constexpr foo ( int const * y ) : single{false}, p{y}
{}
};
constexpr int x{2};
constexpr int y[]{3, 5, 7};
int main ()
{
constexpr foo f1{x};
constexpr foo f3{y};
}