清空一个 C++ 对象

4

通常我会给我的C++对象添加一个Empty方法,使用类似以下代码的方式清除内部状态。

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

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

这似乎比在构造函数中复制代码更好,但我想知道当想要清除一个对象时*this = Foo()是否是一种常见的方法?这样做会不会有什么问题在未来出现?还有其他更好的方法可以实现这种操作吗?

10个回答

11

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

class Foo
{
private:
    int n_;
    std::string str_;
public:
    Foo()
    {
        Reset();
    }

    void Reset()
    {
        n_ = 1234;
        str_ = "Hello, world!";
    }
};

是的,你不必要地先将字符串初始化为空字符串,然后再赋值,但这样更清晰明了。


但是如果 Foo 继承自没有默认构造函数并且需要使用初始化列表进行初始化的 Bar,你会怎么做? - paracycle
如果Bar不需要被重置,那么它在构造时就会被初始化:Foo() : Bar(DUMMY) { Reset(); } 如果Bar需要被重置,我会认为它也会有一个Reset函数:void Reset() { Bar::Reset(); ... } 因此,为了清晰起见,Bar成员可能会有一些冗余的初始化。 - Ates Goral
好观点。我喜欢这种方法用于非性能关键的应用程序。 - paracycle

9

可能出现的问题是什么?你怎样知道*这确实是一个Foo?


6

使用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方法并没有什么优势。它隐藏了在调用它的对象上实际发生的事情,而且名称也不是最好的选择。
如果调用者真的想要一个干净的对象 - 他自己构造对象也不会有问题。

完全同意。这种"对象回收"方式没有任何帮助,反而可能会增加维护和调试的难度。 - Nemanja Trifunovic
也许“清除”会是一个更好的名称——许多STL类型都有一个“clear”方法。 - Rob
具有clear方法的STL对象是容器类,它们可以通过不释放内存来“欺骗”清空。 - Eclipse
为什么要删除 pfoo 呢,当你可以只是 "*pfoo = Foo();"? - Greg Rogers
好的,Greg,我已经将它作为一种替代方案添加了进去。谢谢。 - Paulius

5

另外,考虑将对象设置为不可变的,即在构造时不能更改。在许多情况下,这可以避免意外的副作用。


4

有一种比你所提到的使用swap更常见的方法。

基本上,您可以像这样操作:

T().swap(*this);

由于许多标准容器(所有STL容器?)都有一个常数时间交换方法,因此这是一种清除容器并确保其存储被释放的好方法。

同样,使用复制构造函数而不是默认构造函数也是将容器“收缩到适当大小”的好方法。


是的,这就是我想要发布的内容,呵呵。+1 - Johannes Schaub - litb

3

考虑使用放置 new

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

您的代码调用了operator =,这可能会导致各种副作用。 编辑:根据评论回复。 - 这段代码肯定是定义良好的,标准明确允许它。如果我有时间,我会稍后发布这段话。关于delete - 当然。我的意思是~Foo(),这是一个疏忽。是的,Rob也是正确的;在这里析构对象实际上是必要的,以调用字符串的析构函数。

哦天啊,那看起来真的是一个非常糟糕的主意。你刚刚删除了this指针(也就是你刚刚将那块内存返回给了内存管理器)。如果你幸运的话,程序会很快崩溃。如果不幸的话,你可能会给你程序中的某个部分带来损坏状态。千万不要这样做。 - Michael Kohne
1
你的意思是 "this->~Foo()" 而不是 "delete this"。 - Greg Rogers
即使不删除"this", 仍然是错误的。你正在已初始化的内存上调用构造函数。对象的 std::string 字段将被覆盖为一个新的;它的析构函数不会被首先调用。 - Rob Kennedy
感谢评论。代码出现了问题,现在我已经纠正了它。为自己辩护,我最初理解错了问题,然后重新阅读并编辑了我的帖子。在那次最初的编辑之前,代码实际上是正确的(和现在一样!),我的编辑使它变得更糟。 - Konrad Rudolph
即使不手动执行析构函数,也是有效的(但更糟糕)- 如果析构函数没有你所依赖的副作用,可以省略调用。在这种情况下,也没有未定义行为。尽管如此,在这种情况下,你将省略调用字符串的析构函数,而无法保证它不会引起未定义行为。 - Johannes Schaub - litb
显示剩余3条评论

2

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


2
这是我做的方法:
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);
}

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

swap 设为 Foofriend 怎么样?目前它无法访问私有成员。 - Konrad Rudolph
Konrad,哦对了。我忘记了:)好的,那么我就把它作为一个成员变量,并从自由函数中调用它:)这就是std::string的做法。我想知道为什么它要这样做(有额外的成员变量)。但是这个友元问题可能是关键。 - Johannes Schaub - litb

1
这似乎比在构造函数中复制代码要好,但我想知道当想要清除一个对象时,*this = Foo()是否是一种常见的方法?
清除对象并不是很常见的事情:更常见的是,实例化一个对象(甚至是不可变对象)并包含真实数据,或者没有实例化。
最常重置的类型是容器...但是,你现在不会编写自己的容器类。
这样等待咬我的后面有什么问题吗?
是的:
如果这不是一个Foo而是一个DerivedFoo
如果Foo的赋值运算符不存在,或者它存在缺陷(例如,如果它未定义且默认运算符不好,例如因为数据成员是裸指针)。
还有其他更好的方法来实现这种事情吗?
是的,也许一个自由函数会更好(可以避免上述两个问题):
template<class T> void reconstruct(T* p)
{
    p->~T();
    new (p) T();
}

-2

是的,从性能角度来看这并不高效(创建另一个foo对象而不是在原地工作),如果你在构造函数中分配内存并出现了严重的内存泄漏问题,它会让你吃大亏。

为了使其在内存方面更安全,可以调用this->delete和this = new foo() - 但速度会很慢。

如果你想要超级快速,可以创建一个静态空对象字段,并在Reset中进行memcpy。

如果你只想要快速,可以逐个赋值属性。

如果你想保持合理的风格而不重复调用Reset,可以像Ates Goral建议的那样从Ctor中调用Reset,但你将失去使用默认参数进行更快速的构造的优势。


在非 POD 类型上使用 memcpy 会导致未定义的行为。而分配一个新对象并将结果分配给“this”肯定不会达到您的意图。 - Rob Kennedy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接