C++指向类的指针,在释放类实例后调用函数,这是一个好的做法吗?

3
我是一名有用的助手,可以为您翻译文本。

我有一个类,让我们称之为A。类A有两个子类,分别是ab

我正在创建一个类A的指针,就像这样:

A *pointer;

在程序的某个时刻,我会像这样初始化指针:

pointer = new a();

在其他某个地方,我运行了一个类 A 的函数:
pointer->function(&pointer);

这个函数在类A中(所以所有子类都有它)。当调用这个函数时,有可能我想要将指针切换到另一个子类,以下是我尝试过的方法:

void A::function(A **pointer)
{
    if (something)
    {
        delete *pointer;
        *pointer = new b();
    }
}

虽然这个方法可行,但我真的很好奇这是否是良好的实践。我正在从对象内部调用delete并释放对象本身,这可能是未定义的行为,我运气好才能使它工作吗?我没有正确理解吗?我是不是把它弄得比应该复杂?

2
如果函数在删除*this之后仍然使用它的某些成员,那么你会遇到麻烦。如果不是这样,那为什么要将其作为成员函数呢? - Bo Persson
请看,或者甚至可能是一个重复的问题:https://dev59.com/RHA75IYBdhLWcg3ws7Yh - Petr
1
同时,我建议您将函数更改为A* A::function(const A& obj) { ... return new b(); },并确保旧对象被自动销毁(例如使用智能指针)。 - Petr
@TomTsagk,在所有其他情况下,请只返回 this。我现在正在以答案的形式撰写扩展版本。 - Petr
@BoPersson 这个 function 根据子类使用了许多类成员。我需要它成为类的一部分。 - TomTsagk
显示剩余2条评论
4个回答

3

只要你小心使用,这是有效的。更多讨论请参见有关delete this的特定问题

然而,就像C++中其他需要小心使用的内容一样,最好找到另一个解决方案,以避免出现错误。我建议您将代码改写为返回新指针的函数,并通过智能指针自动销毁旧指针。

示例代码如下:

struct A {
    static std::shared_ptr<A> function(std::shared_ptr<A>& ptr, int x) {
        if (x > 0)
            return std::make_shared<A>(x);
        else return ptr;
    }

    A(int _x): x(_x) {}

    int x;
};

注意,我将function()设置为static,因为它始终将对象作为其第一个参数接受。在coliru上实时查看
事实上,我并不太喜欢使用shared_ptr的这种解决方案,如果有人能提供更好的实现方法,我会很高兴知道。

1
return ptr?这个能编译通过吗? - Support Ukraine
关于delete this的另一个问题有非常好的信息,而且我之前对智能指针一无所知,所以还有更多东西要学习。这个答案似乎是适合我的情况最好的选择。 - TomTsagk
@StillLearning,回答已更新。虽然我也会尝试寻找更好的实现。 - Petr
@Petr 只是好奇为什么你把 A 设计成一个结构体?现在它变得有些复杂了,我有一些解决方案,但它们并不是很优化(过去我是这样做的,函数返回一个指针,然后在 main 函数中,我检查它是否返回 NULL 或另一个指针,在这种情况下,指针指向新对象,但这种方法会导致两个对象在某个时刻同时存在,我不喜欢这种方式(它们有很大的大小),因此我正在寻找一种方法来删除第一个对象,然后创建下一个对象。) - TomTsagk
@Petr 可能只传输少量数据(例如几个 int),但对象本身很大,我只是想避免同时拥有两个活动对象。 - TomTsagk
显示剩余4条评论

1
作为一个设计者,我不喜欢指针突然指向另一个对象(类型不同)而没有明确表明发生了什么。可以说,由于OP的代码传递了&pointer,这表明它可能会改变。然而,我更喜欢赋值 - 我认为这更清晰。
我会尝试类似这样的做法:
int uglyGlobal = 1;  // don't try this at home...  ;-)

class A
{
public:
    int n;
    A() {n = uglyGlobal++; cout << "A cons for #" << n << endl;}
    virtual ~A() {cout << "A des for #" << n << endl;}
    unique_ptr<A> function(int something, unique_ptr<A> ptr);
};

class a : public A
{
public:
    a() {cout << "a cons" << endl;}
    ~a() override {cout << "a des" << endl;}
};
class b : public A
{
public:
    b() {cout << "b cons" << endl;}
    ~b() override {cout << "b des" << endl;}
};

unique_ptr<A> A::function(int something, unique_ptr<A> ptr)
{
    if (something == 0)
    {
        // Turn it into an A
        return unique_ptr<A>(new A);
    }
    else if (something == 1)
    {
        // Turn it into an a
        return unique_ptr<A>(new a);
    }
    else if (something == 2)
    {
        // Turn it into an b
        return unique_ptr<A>(new b);
    }
    else
        // Keep the current
        return ptr;
}

int main()
{
    cout << "Make A" << endl;
    unique_ptr<A> x (new A);

    cout << "1. call - turn A into a" << endl;
    x = x->function(1, move(x));

    cout << "2. call - turn a into b" << endl;
    x = x->function(2, move(x));

    cout << "3. call - turn b into another b" << endl;
    x = x->function(2, move(x));

    cout << "4. call - keep current b" << endl;
    x = x->function(3, move(x));

    cout << "Return from main" << endl;
    return 0;
}

输出结果为:

Make A
A cons for #1
1. call - turn A into a
A cons for #2
a cons
A des for #1
2. call - turn a into b
A cons for #3
b cons
a des
A des for #2
3. call - turn b into another b
A cons for #4
b cons
b des
A des for #3
4. call - keep current b
Return from main
b des
A des for #4

这与其他建议使用智能指针的答案类似,我喜欢分配的想法,正如你所说的那样更清晰,但通过传递&pointer,我避免了使用智能指针,您是否仅建议出于设计目的使用此方法?因此,我的代码是安全的,只是从设计上来说不是一个好主意?请注意,类名(任何变量)都是为了本问题的目的而简化的(我实际上没有将我的类命名为Aa:P)。 - TomTsagk
@TomTsagk- 最初我并不打算回答。我很喜欢Petr发布的第一个答案。但由于编译问题,Petr对答案进行了相当大的修改,并且我认为新答案并不如第一个好。由于alangab提供的答案有点不完整,它没有处理您不想要指向新对象实例的情况。因此,我决定发布这个答案,它基于Petr的原始答案。(更多内容见下一条评论...) - Support Ukraine
@TomTsagk - 关于智能指针:不要认为它像是“我避免使用智能指针”!相反,你应该把智能指针看作是你的朋友。它们帮助你跟踪分配的内存,并确保在需要时调用析构函数。尽可能避免使用裸指针。 - Support Ukraine
当然,智能指针很有用,但我是从Java转到C++的,我厌倦了Java为我处理一切,我喜欢在C/C++中拥有更多的控制权,这既是好事也是坏事。我目前正在网上阅读关于智能指针的资料(我今天才发现它们),我还不知道是否会使用它们。 - TomTsagk
+1 -- move 的技巧是我之前忽略的。事实上,我尝试过使用它,但无法使其正常工作。 - Petr

1

这段代码是有效的(有关正确性的更多信息,请参见this answer)。
但这不是一个好的实践,因为其他开发人员可能会忽略细微差别,使用其中一个成员函数将导致对象重建。
最好明确地重建对象,而不是将其隐藏在成员函数中。 或者只需使用智能指针。


0
一般来说,您必须确保调用function的每个执行路径都没有A类或其派生类方法的堆栈帧(this是无效的),因为这很危险。在MFC API编程中,这通常发生在WM_NCDESTROY消息处理程序中。在其中,您会执行像delete this这样的操作,但Windows会确保WM_NCDESTROY是发送到窗口的最后一条消息。
我建议您稍微更改A类的API并使用unique_ptr来处理内存:
#include <memory>

class A
{

public:

    std::unique_ptr<A> f()
    {
        return std::make_unique<A>();
    }
};

int main()
{
    auto p =  std::make_unique<A>();
    p = std::move(p->f());
    return 0;
}

通过这种方式,你将销毁从f()内部移动到了p的赋值操作中。


非常感谢您的回答,但是另一个答案已经建议使用智能指针(unique_ptr)。 - TomTsagk

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