【问题标题】:Emptying a C++ object清空 C++ 对象
【发布时间】:2010-09-30 23:29:19
【问题描述】:

我经常向我的 C++ 对象添加一个Empty 方法,以使用类似于以下的代码来清除内部状态。

class Foo
{
private:
    int n_;
    std::string str_;
public:
    Foo() : n_(1234), str_("Hello, world!")
    {
    }

    void Empty()
    {
        *this = Foo();
    }
};

这似乎比在构造函数中复制代码要好,但我想知道*this = Foo() 是否是想要清除对象时的常用方法?这个等着咬我有什么问题吗?还有其他更好的方法来实现这种事情吗?

【问题讨论】:

    标签: c++ construction


    【解决方案1】:

    我会让构造函数调用我的函数:

    class Foo
    {
    private:
        int n_;
        std::string str_;
    public:
        Foo()
        {
            Reset();
        }
    
        void Reset()
        {
            n_ = 1234;
            str_ = "Hello, world!";
        }
    };
    

    是的,您不必要地先将字符串初始化为空字符串,然后进行赋值,但这更清楚。

    【讨论】:

    • 但是如果Foo 继承自Bar 没有默认构造函数并且需要使用初始化列表进行初始化,你会怎么做?
    • 如果 Bar 也不需要被重置,它只会在构造时被初始化:Foo() : Bar(DUMMY) { Reset(); } 如果 Bar 需要被重置,我假设它会有一个 Reset 函数好吧:void Reset() { Bar::Reset(); ... }。因此,为了清楚起见,可能对 Bar 成员进行了一些冗余初始化。
    • 好点。对于非性能关键型应用程序,我喜欢这种方法。
    【解决方案2】:

    另外,考虑使对象不可变,,当它们被构造时,它们不能被改变。在很多情况下,这可以让您避免意外的副作用。

    【讨论】:

      【解决方案3】:

      考虑使用展示位置new

      void Empty() {
          this->~Foo();
          new (this) Foo();
      }
      

      您的代码调用了operator =,这可能会导致各种副作用。

      EDIT 响应 cmets。 – 此代码绝对定义明确,标准明确允许。如果我有时间,我将在稍后发布该段落。关于delete——当然。我的意思是~Foo(),这是一个疏忽。是的,Rob 也是对的。这里实际上需要破坏对象来调用字符串的析构函数。

      【讨论】:

      • 天哪。这看起来是个非常糟糕的主意。您刚刚删除了 this 指针(即您刚刚将该内存返回给内存管理器)。如果你幸运的话,你会很快崩溃。如果没有,您只会将损坏的状态提供给知道您程序的哪个部分的人。不要那样做。
      • 你的意思是“this->~Foo()”而不是“delete this”。
      • 即使不删除“this”,它仍然是错误的。您在已经初始化的内存上调用构造函数。对象的 std::string 字段将被新字段覆盖;它的析构函数不会被首先调用。
      • 感谢 cmets。代码很糟糕,我现在已经更正了。在我的辩护中,我最初理解这个问题是错误的,然后重新阅读并编辑了我的帖子。在最初的编辑之前,代码实际上是正确的(和现在一样!),我的编辑我对其进行了改进。
      • herb sutter 不建议这样做:gotw.ca/publications/advice97.htm。这就是说 - 是的,它定义明确的,但是一旦你从 Foo 派生出来就会失败,例如。 Herb 比我更懂事,还收集了其他问题。
      【解决方案4】:

      是的,这在性能方面效率不高(创建另一个 foo 对象而不是在原地工作),如果你在构造函数中分配内存并导致严重的内存泄漏,它会咬你。

      在内存方面使其更安全将调用 this->delete 和 this = new foo() - 但它会很慢。

      如果您想超快,请创建一个静态空对象字段并在 Reset 中对其进行 memcpy。

      如果您只想快速地一一分配属性。

      如果您想保持合理的风格而不重复,请按照 Ates Goral 的建议从 Ctor 调用 Reset,但您将失去使用默认参数的更快构造。

      【讨论】:

      • Memcpy 会导致非 POD 类型的未定义行为。并且分配一个新对象并将结果分配给“this”肯定不会达到您的预期。
      【解决方案5】:

      潜在问题?你怎么知道 *this 真的是 Foo?

      【讨论】:

        【解决方案6】:

        有些东西比你建议的更常见。使用交换。

        基本上你会这样做:

        T().swap(*this);
        

        由于许多标准容器(所有 STL 容器?)都具有恒定时间交换方法,因此这是清除容器并确保释放其存储空间的好方法。

        同样,这也是“缩小以适应”容器的好方法,但使用复制构造函数而不是默认构造函数。

        【讨论】:

        • 是的,这就是我现在想发布的内容,呵呵。 +1
        【解决方案7】:

        您使用此 Empty 方法所做的工作,本质上与手动将新构造的对象分配给变量(Empty 函数所做的事情)相同。

        就个人而言,我会删除 Empty 方法,并将该方法的所有用法替换为:

        // let's say, that you have variables foo and pfoo - they are properly initialized.
        Foo foo, *pfoo;
        
        // replace line "foo.Empty()" with:
        foo = Foo();
        
        // replace line "pfoo->Empty()" with:
        delete pfoo;
        pfoo = new Foo();
        // or
        *pfoo = Foo();
        

        我真的认为使用这种 Empty 方法没有任何优势。它隐藏了被称为女巫的对象的真实情况,名称也不是最佳选择。

        如果调用者真的想要一个干净的对象 - 他自己构造对象是没有问题的。

        【讨论】:

        • 正是我的想法。这种“对象回收”不会有任何帮助,并且可能会使维护和调试变得更加困难。
        • 也许 'Clear' 会是一个更好的名字 - 许多 STL 类型都有一个 'clear' 方法。
        • 具有 clear 方法的 stl 对象是容器类,可以通过不释放内存来“欺骗”清除。
        • 当你可以“*pfoo = Foo();”时为什么要删除pfoo
        • 很好,格雷格,我添加了它作为替代。谢谢。
        【解决方案8】:

        这似乎比在构造函数中复制代码要好,但我想知道 *this = Foo() 是否是想要清除对象时的常用方法?

        清除一个对象并不常见:更常见的是,一个对象(甚至可能是一个不可变对象)被实例化并包含真实数据,或者它没有被实例化。

        重置的最常见的东西是容器......但是,您现在不会编写自己的容器类了,对吧。

        这个等着咬我有什么问题吗?

        是的:

        • 如果这不是真正的Foo,而是DerivedFoo
        • 如果Foo 的赋值运算符不存在,或者它有问题(例如,如果它没有定义并且默认运算符不好,例如因为数据成员是裸指针)。

        还有其他更好的方法来实现这种事情吗?

        是的,也许免费功能会更好(可以避免上述两个问题):

        template<class T> void reconstruct(T* p)
        {
            p->~T();
            new (p) T();
        }
        

        【讨论】:

          【解决方案9】:

          如果您在构造函数中有动态分配的内存,这可能是内存泄漏的潜在来源。

          【讨论】:

            【解决方案10】:

            我是这样做的:

            class Foo {
            private:
                int n_;
                std::string str_;
            public:
                Foo() : n_(1234), str_("Hello, world!")
                {
                }
            
                void Empty()
                {
                    Foo f;
                    swap(f);
                }
            
                void swap(Foo & other) {
                    std::swap(n_, other.n_);
                    swap(str_, other.str_);
                }
            };
            
            void swap(Foo & one, Foo & other) {
                one.swap(other);
            }
            

            将交换函数放入与 Foo 类相同的命名空间中。当用户调用 swap 以交换两个 Foo 时,参数相关查找会找到它。你也可以使用你的交换函数来实现operator=

            【讨论】:

            • swap 成为friendFoo 怎么样?目前,它无法访问私有成员。
            • 康拉德,哦,对了。我错过了:) 好的,那么我就把它作为一个成员并从免费函数中调用它:) 这就是 std::string 的方式。我想知道为什么会这样做(有那个额外的成员)。但这个朋友问题可能是关键
            猜你喜欢
            • 2017-01-12
            • 1970-01-01
            • 2016-05-12
            • 2011-12-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-01
            相关资源
            最近更新 更多