【问题标题】:Dedicating a field in arbitrary class types for "external use"将任意类类型中的字段专用于“外部使用”
【发布时间】:2025-11-27 21:00:01
【问题描述】:

我的容器需要存储一些关于其元素的信息。通常,我将其与元素分开存储。但是,我想通过将元素结构类型中的一个字段专用于外部使用,为用户提供节省内存的可能性。例如:

struct MyStuff
{
  int           foo;
  char          bar;
  mutable char  dedicated_for_external_use;  // Because of alignment, this field
                                             // won't increase sizeof (MyStuff)
};

这里的想法是,除了元素的容器之外,任何东西都不能访问该字段。由于容器存储一个副本(很像std::vector),因此如果您将任何给定值x 添加到多个容器中,这不会有问题。

如果可能的话,您将如何为此设计一个满足以下要求的界面?

  • 应该是完全可选的。 IE。应该可以自动确定给定类型是否提供这样的字段,然后容器只会在可用时使用它。
  • 理想情况下,不依赖于类型特征等,因为我需要最大的编译器兼容性。
  • 应该易于使用。 IE。如果您可以并且想要为 MyStuff 类型启用此优化,您可以使用 3 行代码而不是 25 行代码来实现。另一方面,内部复杂性并不重要。
  • 最好完全排除误报。我的意思是:如果您检查字段foo_bar,则存在这种字段的可能性很小,原因完全不相关(我认为鸭式打字根本不适用于C++)。更好的方法是检查类型是否从我的库中继承标记类 ProvidesExternalUseField,因为这不是偶然的。

编辑

我知道 Boost.Intrusive,但我想要的是不同的东西。如果我这样做并创建一个带有单个 char 字段的钩子类,则在许多情况下它不能用于节省内存。如果继承类型有一个int 作为第一个字段,char 字段将被填充为 4 个字节。 IE。你通常需要复杂的类型内部知识才能“挤压”这样的外部使用字段,但继承并没有真正提供它:

struct hooks { mutable char dedicated_for_external_use; };
struct MyStuff : hooks
{
  int           foo;
  char          bar;
};

这里,MyStuff 的大小将是 12 个字节,而不是 8 个。

【问题讨论】:

  • 也许 Boost.Intrusive (boost.org/doc/libs/1_43_0/doc/html/intrusive.html) 和 boost::intrusive_ptr (boost.org/doc/libs/1_43_0/libs/smart_ptr/intrusive_ptr.html) 可能会给一些启发。
  • 当您说“最大编译器兼容性”时,这在实践中意味着什么?您需要使用它的编译器有多糟糕?
  • @jalf:嗯,最好它应该在兼容 C++98 的编译器上工作。我只是说如果可能的话,我宁愿不使用任何非标准扩展或 C++0x 的东西。
  • @Philipp:谢谢,我知道 Boost.Intrusive,但我想要的有点不同。我会更好地更新问题。
  • 那么类型特征使用起来非常安全。它们仅依赖于 C++98 特性。所以定义一个像has_extra_field<T>这样的特征

标签: c++ interface-design


【解决方案1】:

对于从标记接口派生的数据结构的情况,您可以使用部分模板特化。

假设您的标记接口类如下所示:

class ProvidesExternalUseField
{
public:
    char GetExtraField () { return 0; }
    void SetExtraField (char newVal) {}
};

出于某种目的,它不是虚拟的:我们不希望为此添加指向数据类的 vtable 指针。

现在让我们实现一个简单的容器类:

template <class T>
class Container
{
public:
    char GetExtraValue ()
    {
        return 0; // here we cannot know if T is derived from the marker
    }
private:
    T m_t;
};

以下是我们如何更改它以区分这两种情况:

template <class T, bool DoesTProvideExternalUseField>
class ContainerImpl
{
public:
    char GetExtraValue () { return 0; }

private:
    T m_t;
};

template <class T>
class ContainerImpl<T, true>
{
public:
    char GetExtraValue () { return m_t.GetExtraField(); } 
private:
    T m_t;
};

template <class T>
class Container: public ContainerImpl<T,
                                      boost::is_base_of<ProvidesExternalUseField,T>::value>
{
};

现在您可以像这样定义结构:

struct A
{
    int m_intVal;
};

struct B: public ProvidesExternalUseField
{
    char GetExtraField () { return m_extraField; }
    void SetExtraField (char newVal) { m_extraField = newVal; }

    int m_intVal;
    char m_charVal;
    char m_extraField;
};

并以完全相同的方式使用容器类:

Container<A> a;
Container<B> b;

您还可以通过使用指向成员的指针作为模板参数,进一步自动化(模板化)标记界面中的 getter 和 setter。

【讨论】:

    最近更新 更多