【问题标题】:How to do the equivalent of memset(this, ...) without clobbering the vtbl?如何在不破坏 vtbl 的情况下做相当于 memset(this, ...) 的操作?
【发布时间】:2012-10-15 19:28:15
【问题描述】:

我知道 memset 在 class 初始化时不受欢迎。例如,如下所示:

class X { public: 
X() { memset( this, 0, sizeof(*this) ) ; }
...
} ;

如果组合中有 virtual 函数,将破坏 vtbl

我正在开发一个(庞大的)遗留代码库,它是 C-ish 但用 C++ 编译的,所以所有有问题的成员通常都是 POD,不需要传统的 C++ 构造函数。 C++ 的使用逐渐普及(就像虚函数一样),这让那些没有意识到 memset 具有这些额外的 C++ 优势的开发人员陷入了困境。

我想知道是否有一种 C++ 安全的方法来进行初始的全覆盖零初始化,然后可以在不适合零初始化的情况下进行特定的成员初始化?

我发现了类似的问题memset for initialization in C++zeroing derived struct using memset。这两个都有“不要使用 memset()”的答案,但没有好的选择(尤其是对于可能包含许多成员的大型结构)。

【问题讨论】:

  • 一般你取第一个字段的地址,然后从那里清除。毫无疑问,这违反了一些规则,但它总是对我有用。
  • 另一种选择是为类编写自己的分配例程,这样您就可以确保空间被清除,但 IIRC 对此有限制。
  • 你不是说memset(this, 0, sizeof *this)吗? (是的,这也会破坏 vtbl。)
  • @HotLicks,似乎这种方法的问题在于知道正确的大小。 sizeof(this) 不可能与 sizeof() 相同。
  • 这不仅仅是关于 vtable,你还可以有非标准的布局成员,memset 也会搞砸。做出选择:用 C 编写(并删除 C++ 部分)或用 C++ 编写(并使用 C++ 习语)。将这两种东西混合起来很危险,而且可能更容易(也更优越)开始花时间一次修复这些类,以便为它们提供适当的构造函数,而无需花言巧语。

标签: c++ initialization


【解决方案1】:

对于每个找到memset 调用的类,添加一个memset 成员函数,该函数忽略指针和大小参数并对所有数据成员进行赋值。

编辑: 实际上,它不应该忽略指针,它应该将它与this 进行比较。在匹配时,为对象做正确的事情,在不匹配时,重新路由到全局函数。

【讨论】:

  • 是的,有一些优点。比在 5 个不同的构造函数中管理列表更容易记住保持更新。
【解决方案2】:

您总是可以将构造函数添加到这些嵌入式结构中,因此可以说它们会自行清除。

【讨论】:

  • 问题实际上是关于在构造函数中放入什么(也就是说,不是 100 个赋值语句)。
【解决方案3】:

试试这个:

template <class T>
void reset(T& t)
{
   t = T();
}

这将使您的对象归零 - 无论它是否是 POD。

但不要这样做:

   A::A() { reset(*this); }

这将在无限递归中调用A::A!!!

试试这个:

  struct AData { ... all A members };
  class  A { 
   public: 
      A() { reset(data); } 
   private: 
      AData data; 
   };

【讨论】:

  • boost value_initialized: boost.org/doc/libs/1_38_0/libs/utility/value_init.htm 是这样做的吗?
  • @PeeterJoot 或多或少确实如此。实际上它以更复杂的方式进行,这个 boost 库声称由于一些编译器问题,并且因为我的答案中提出的方式并不总是非 POD 类型的最有效方式,它为此目的使用静态常量变量。但是,如果您寻求 memset 替换 - 我的“简化”方式就足够了。
  • t = T(); 这不会将对象归零,而是将其替换为默认构造的对象。如果默认构造函数未初始化成员,则该成员仍将保持未初始化状态。如果它将某些东西初始化为非零,它也将保持该值。重置为 default 而不是 all zeros 实际上可能是一件好事,但它们与您建议的不同。
  • @Shahbaz - 它将 C++ 意义上的对象归零:对于 POD 类型 - 所有数字为零,布尔为 false,指向空值的指针。对于非 POD(您描述的情况),将使用默认构造的对象-因此,如果有人以错误的方式编写默认构造函数-那么没办法-它将失败。但是请参阅我提供的示例 - AData 被定义为简单聚合,因此如果它的成员是具有有效默认构造函数的 POD 或非 POD - 它将始终有效。
【解决方案4】:

这很可怕,但是您可以为这些对象(或在公共基类中)重载operator new/delete,并让实现提供零缓冲。像这样:

class HideousBaseClass
{
public:
    void* operator new( size_t nSize )
    {
        void* p = malloc( nSize );
        memset( p, 0, nSize );
        return p;
    }
    void operator delete( void* p )
    {
        if( p )
            free( p );
    }
};

也可以覆盖 global new/delete 运算符,但这可能会对性能产生负面影响。

编辑:我刚刚意识到这种方法不适用于堆栈分配的对象。

【讨论】:

    【解决方案5】:

    利用静态实例初始化为零的事实: https://ideone.com/GEFKG0

    template <class T>
    struct clearable
    {
        void clear()
        {
            static T _clear;
            *((T*)this) = _clear;
        };
    };
    
    class test : public clearable<test>
    {
        public:
            int a;
    };
    
    int main()
    {
        test _test;
        _test.a=3;
        _test.clear();
    
        printf("%d", _test.a);
    
        return 0;
    }
    

    但是,上述操作会导致(模板化类的)构造函数被第二次调用。

    对于导致没有 ctor 调用的解决方案,可以改用:https://ideone.com/qTO6ka

    template <class T>
    struct clearable
    {
        void *cleared;
        clearable():cleared(calloc(sizeof(T), 1)) {}
    
        void clear()
        {
            *((T*)this) = *((T*)cleared);
        };
    };
    

    ...如果您使用的是 C++11 及更高版本,则可以使用以下内容:https://ideone.com/S1ae8G

    template <class T>
    struct clearable
    {
        void clear()
        {
            *((T*)this) = {};
        };
    };
    

    【讨论】:

      【解决方案6】:

      我能找到的更好的解决方案是创建一个单独的结构,您将在其中将必须 memsetted 的成员设置为零。不确定这种设计是否适合您。

      这个结构没有 vtable 并且没有扩展。这将只是一大块数据。这种方式 memsetting 结构是安全的。

      我做了一个例子:

      #include <iostream>
      #include <cstring>
      
      struct X_c_stuff {
          X_c_stuff() {
              memset(this,0,sizeof(this));
          }
          int cMember;
      };
      class X : private X_c_stuff{
      public:
          X() 
          : normalMember(3)
          {
              std::cout << cMember << normalMember << std::endl;
          }
      private:
          int normalMember;
      };
      
      int main() {
          X a;
          return 0;
      }
      

      【讨论】:

        【解决方案7】:

        您可以使用指针算法来查找要归零的字节范围:

        class Thing {
        public:
            Thing() {
                memset(&data1, 0, (char*)&lastdata - (char*)&data1 + sizeof(lastdata));
            }
        private:
            int data1;
            int data2;
            int data3;
            // ...
            int lastdata;
        };
        

        (编辑:我最初为此使用了offsetof(),但有评论指出这仅适用于POD,然后我意识到您可以直接使用会员地址。)

        【讨论】:

          猜你喜欢
          • 2011-08-19
          • 2010-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多