【问题标题】:Erase all members of a class擦除类的所有成员
【发布时间】:2009-01-31 15:36:34
【问题描述】:

昨天我读了一个同事的一些代码,发现了这个:

class a_class
{
public:
    a_class() {...}
    int some_method(int some_param) {...}

    int value_1;
    int value_2;
    float value_3;
    std::vector<some_other_class*> even_more_values;
    /* and so on */
 }

 a_class a_instances[10];

 void some_function()
 {
     do_stuff();
     do_more_stuff();

     memset(a_instances, 0, 10 * sizeof(a_class)); // <===== WTF?
 }

这合法吗(WTF 行,而不是公共属性)?对我来说,它闻起来真的,真的很糟糕...... 该代码在使用 VC8 编译时运行良好,但在调用 a_instances[0].event_more_values.push_back(whatever) 时使用 VC9 编译时会引发“意外异常”,但在访问任何其他成员时。有什么见解吗?

编辑:将 memset 从 memset(&amp;a_instances... 更改为 memset(a_instances...。感谢您指出爱德华。
EDIT2:删除了 ctor 的返回类型。谢谢。

结论:谢谢各位,你证实了我的怀疑。

【问题讨论】:

    标签: c++


    【解决方案1】:

    这是一种被广泛接受的 C 结构初始化方法。
    在 C++ 中它当然不起作用,因为你不能假设任何关于 vectors 内部结构的事情。将其归零很可能会使其处于非法状态,这就是您的程序崩溃的原因。

    【讨论】:

      【解决方案2】:

      他在非 POD 类类型上使用 memset。这是无效的,因为 C++ 只允许在最简单的情况下使用它:类没有用户声明的构造函数、析构函数、没有虚函数和更多限制。它的对象数组不会改变这一事实。

      如果他删除了向量,他可以在上面使用 memset。一张纸条。即使它不是 C++,它也可能对他的编译器有效——因为如果标准说某些东西具有未定义的行为,实现可以做他们想做的一切——包括支持这种行为并说明会发生什么。在他的情况下,发生的事情可能是您在其上应用 memset,它会默默地清除向量的任何成员。其中可能指向已分配内存的指针现在将只包含零,而它并不知道。

      你可以推荐他用这样的东西来清除它:

      ...
      for(size_t i=0; i < 10; i++)
          objects[i].clear();
      

      并使用类似的东西写清楚:

      void clear() {
          a_object o;
          o.swap(*this);
      }
      

      交换只会将 o 的向量与 *this 的向量交换,并清除其他变量。交换向量特别便宜。他当然需要编写一个交换函数,交换向量 (even_more_values.swap(that.even_more_values)) 和其他变量。

      【讨论】:

      • 别管 ctor 的返回类型。我从头顶写了上面的代码。
      • 正如我在给 Pierre 的评论中所写,该类已经有一个正确的 clear() 方法,但他没有使用它。在我告诉他我对 memset 感到头疼之后,他按照你的建议将代码更改为 for 语句。
      【解决方案3】:

      我不确定,但我认为 memset 会擦除向量的内部数据。

      【讨论】:

        【解决方案4】:

        当将 a_instances 归零时,您还将其中的 std_vector 归零。它可能在构造时分配一个缓冲区。现在,当您尝试 push_back 时,它会看到指向缓冲区的指针为 NULL(或其他一些内部成员),因此会引发异常。

        如果你问,这是不合法的。那是因为你不能通过指针重载写入,因为你可以重载赋值运算符。

        【讨论】:

        • 好的,你找到了。当然在原始代码中它不是 &a_instances,而是 a_instances。相应地更改了代码。谢谢。
        • 好的,我要改答案了。
        【解决方案5】:

        最糟糕的是,如果向量中包含任何内容,则该内存现在会丢失,因为没有调用构造函数。

        永远不要覆盖 C++ 对象。曾经。如果它是派生对象(我不知道 std::vector 的细节),此代码还会覆盖对象的 vtable,使其崩溃和损坏。

        写这篇文章的人不明白什么是对象,需要你解释它们是什么以及它们是如何工作的,这样他们以后就不会犯这种错误。

        【讨论】:

        • 那不应该覆盖非 POD 对象。有关 POD 是什么,请参阅 litb 的答案。
        • 我怀疑与即将发生的访问冲突相比,丢失向量中预分配缓冲区的几个字确实是一个很大的问题。令人惊讶的是,这曾经奏效过。
        • 问题是,如果 std::vector 没有 vtable(因此没有间接),并且它碰巧使用 NULL 来表示它的内部指针没有被填充,那么你可能会逃脱将其全部归零。你仍然会失去记忆,但你可能不会崩溃。
        【解决方案6】:

        您不应该对 C++ 对象执行 memset,因为它不会调用正确的构造函数或析构函数。

        特别是在这种情况下,不调用所有 a_instances 元素的 even_more_values 成员的析构函数。

        实际上,至少对于您列出的成员(在 /* 等 */ 之前),您不需要调用 memset 或创建任何特殊的析构函数或 clear() 函数。默认析构函数会自动删除所有这些成员。

        【讨论】:

        • ::shrugs:: 我明白了。只是在一个慵懒的星期六早上感觉相反。
        • 这就是我的想法。但是当你这样做时会发生什么?
        • 这不会覆盖对象本身。 &a_instances 是一个指针,仔细看。
        • 不用担心。 Eduard 也错了:&a_instances 是一个指向数组的指针。不是指向指针的指针(因为 a_instances 不是指针)
        【解决方案7】:

        你应该在你的类中实现一个方法'clear'

        void clear()
          {
          value1=0;
          value2=0;
          value_3=0f;
          even_more_values.clear();
          }
        

        【讨论】:

        • 不需要清除。 even_more_values 是向量,而不是指针
        • 即便如此,clear() 也不会释放 even_more_values 中包含的对象。原始问题中的一个大问题是分配对象的所有权......
        【解决方案8】:

        你在这里的东西可能不会崩溃,但它也可能不会做你想做的事!将向量归零不会为每个 a_class 实例调用析构函数。它还将覆盖a_class.even_more_values 的内部数据(因此,如果您的push_back()memset() 之后,您可能会遇到访问冲突)。

        我会做两件不同的事情:

        1. a_classsome_function() 中都使用std::vector 进行存储。
        2. a_class 写一个析构函数,可以正确清理

        如果您这样做,编译器将自动为您管理存储。

        例如:

        class a_class
        {
        public:
            a_class() {...}
            ~a_class() { /* make sure that even_more_values gets cleaned up properly */ }
        
            int some_method(int some_param) {...}
        
            int value_1;
            int value_2;
            float value_3;
            std::vector<some_other_class*> even_more_values;
            /* and so on */
         }
        
         void some_function()
         {
             std::vector<a_class> a_instances( 10 );
        
             // Pass a_instances into these functions by reference rather than by using
             // a global. This is re-entrant and more likely to be thread-safe.
             do_stuff( a_instances );
             do_more_stuff( a_instances );
        
             // a_instances will be cleaned up automatically here. This also allows you some
             // weak exception safety.
         }
        

        请记住,如果even_more_values 包含指向其他对象的指针,则需要在a_class 的析构函数中删除这些对象。如果可能,even_more_values 应该包含对象本身而不是指向这些对象的指针(这样您可能不必为 a_class 编写析构函数,编译器为您提供的析构函数可能就足够了)。

        【讨论】:

          猜你喜欢
          • 2016-09-08
          • 2012-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-27
          • 1970-01-01
          • 2019-03-22
          相关资源
          最近更新 更多