【问题标题】:C++ pointer to class, call function where class-instance is freed, is that good practice?指向类的 C++ 指针,调用释放类实例的函数,这是一种好习惯吗?
【发布时间】:2015-12-11 07:57:30
【问题描述】:

我有一堂课,我们称之为AA 类有两个子类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 并释放对象本身,这可能是未定义的行为,我很幸运它有效吗?我不明白这一点吗?我是否让这变得比应有的复杂?

【问题讨论】:

  • 如果函数使用*this的某些成员被删除后,你将陷入困境。如果不是,为什么是成员函数?
  • 同时我建议您将函数更改为A* A::function(const A& obj) { ... return new b(); },并确保旧对象自动销毁(例如使用智能指针)。
  • @Petr 我想到了这个,问题是function 运行了很多次,它很少改变pointer。如果我让function 返回指向新对象的指针,我应该一直比较它是否返回NULL 或不对?
  • @TomTsagk,在所有其他情况下只返回this。我现在正在写一个扩展版本作为答案。

标签: c++ class pointers


【解决方案1】:

是的,只要你小心,这是有效的。在a question specifically about 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,因为它无论如何都接受对象作为其第一个参数。见live on coliru

事实上,我不太喜欢shared_ptr 的这种解决方案,如果有人能更好地实现这种方法,我会很高兴知道。

【讨论】:

  • return ptr ?这可以编译吗?
  • 另一个关于delete this的问题有很好的信息,而且我对智能指针一无所知,所以我有更多的东西要研究,这个答案似乎最适合我的情况。跨度>
  • @StillLearning,哦,我的错,我重做了太多。现在将更新。
  • @StillLearning,更新了答案。虽然我也会尝试找到更好的实现。
  • @Petr - 根据您的第一个代码建议,我想您可以通过移动将唯一指针传递给函数,即a-&gt;function(move(a)),然后在错误部分再次返回它if 语句。我想那会奏效。无论如何,我永远不会编写 OP 建议的代码。
【解决方案2】:

此代码有效(有关正确性的更多信息,请参阅this answer)。
但这不是一个好的做法,因为其他开发人员可能会忽略细微差别,使用其中一个成员函数会导致对象的重建。
显式重构对象比将其隐藏在成员函数中要好。 或者只使用智能指针。

【讨论】:

    【解决方案3】:

    作为一种设计,我不喜欢指针在不清楚是否发生时突然指向另一个对象(不同类型的)。可以说,由于 OP 代码通过了&amp;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
    

    【讨论】:

    • 这类似于其他建议智能指针的答案,我喜欢分配的想法,正如你所说的那样更清楚,但是通过传递 &amp;pointer 我避免使用智能指针,你是否只建议这种方式出于设计目的?所以我的代码是安全的,只是在设计方面不是一个好主意?请注意,出于这个问题的目的,类名(任何变量)都被简化了(我实际上并没有将我的类命名为 Aa :P)。
    • @TomTsagk - 最初我根本不打算发布答案。我喜欢 Petr 发布的第一个答案。但是由于编译问题,Petr 改变了很多答案,我认为新的答案不如第一个好。 alangab 的答案有点不完整,因为它无法处理您不希望指针指向新对象实例的情况。因此,我决定发布这个基于 Petr 原始答案的答案。 (更多在下一条评论...)
    • @TomTsagk - 关于智能指针:不要把它想成I avoid using smart pointers!相反,您应该将智能指针视为您的朋友。它们帮助您跟踪分配的内存并确保根据需要调用析构函数。尽可能避免使用原始指针。
    • 当然智能指针很有用,但我是从 Java 来到 C++ 的,我厌倦了 Java 为我处理所有事情,我喜欢 C/C++ 我有更多的权力,这两者兼而有之好和坏。我目前正在网上阅读有关智能指针的信息(我今天发现了它们)我不知道我是否会使用它们(还)。
    • +1 -- move 的诀窍是我缺少的东西。事实上,我确实尝试过,但无法让它发挥作用。
    【解决方案4】:

    通常,您必须确保调用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)。
    猜你喜欢
    • 1970-01-01
    • 2011-08-30
    • 2016-06-01
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 2017-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多