3

I have a class, let's call it A. There are two subclasses of class A which are a and b.

I'm making a pointer of class A like this:

A *pointer;

At some point in the program I initialize the pointer like this:

pointer = new a();

At some other point, I run a function of class A:

pointer->function(&pointer);

This function is inside class A (so all subclasses have it). There is a chance that when this function is called, I want to change pointer to another subclass, here is what I tried:

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

Although this works, I'm really curious if this is good practice, I'm calling delete from inside the object and freeing the object itself, could this be undefined behavior and I got lucky it worked? Am I not understanding this right? Am I making this more complicated than it should be?

TomTsagk
  • 1,474
  • 9
  • 23
  • 2
    If the function uses some members of `*this` after is is deleted, you will be in deep trouble. If not, why is it a member function? – Bo Persson Dec 11 '15 at 08:09
  • See, or maybe even a duplicate: http://stackoverflow.com/questions/3150942/c-delete-this – Petr Dec 11 '15 at 08:10
  • 1
    While at the same time I would suggest you change the function into `A* A::function(const A& obj) { ... return new b(); }`, and make sure the old object is destroyed automatically (by using smart pointers for example). – Petr Dec 11 '15 at 08:11
  • @Petr I thought of this, the problem is that the `function` runs many times and it changes the `pointer` rarely. If I made the `function` to return a pointer to the new object, I should make compares all the time if it returned `NULL` or not right ? – TomTsagk Dec 11 '15 at 08:15
  • @TomTsagk, just return `this` in all other cases. I'm now writing an expanded version as an answer. – Petr Dec 11 '15 at 08:15
  • @BoPersson the `function` uses many members of the class depending on the subclass. I need it to be part of the classes. – TomTsagk Dec 11 '15 at 08:15
  • @Petr ooh returning `this` actually sounds good, I might do it like that (I'm still curious about this issue) – TomTsagk Dec 11 '15 at 08:17

4 Answers4

3

Yes, that's valid as long as you are careful. See more discussion at a question specifically about delete this.

However, as with other things in C++ which are valid as long as you are careful, you are better to find another solution that will be less prone to errors. I suggest you reworking you code into a function returning a new pointer, and having the old one automatically destroyed (via a smart pointer, for example).

Something along the lines:

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;
};

Note also I made function() to be static, as it anyway accepts the object as its first argument. See live on coliru.

In fact, I don't quite like this solution with shared_ptr, and if someone will give a better implementation of this approach, I'll be glad to know.

Community
  • 1
  • 1
Petr
  • 9,812
  • 1
  • 28
  • 52
  • 1
    `return ptr` ? Does this compile? – Support Ukraine Dec 11 '15 at 08:27
  • The other question about `delete this` has really good information, also I didn't know anything about smart pointers, so I have more things to study, this answer seems the best for my case. – TomTsagk Dec 11 '15 at 08:28
  • @StillLearning, oh, my fault, I have reworked this too much. Will update this now. – Petr Dec 11 '15 at 08:28
  • @StillLearning, updated the answer. Though I'll also try to find a better implementation. – Petr Dec 11 '15 at 08:41
  • @Petr - With your first code suggestion, I guess you could have passed the unique pointer to the function using a move, i.e. `a->function(move(a))` and then just return it again in the false-part of the if-statement. I guess that would work. In any case, I would never write such code as OP suggest. – Support Ukraine Dec 11 '15 at 08:43
  • @Petr Just curious why did you make `A` a struct? I think it got a little more complicated now, I have some solutions in mind but they are not that optimized (in the past I was doing it like the function returns a pointer and then on `main` I check if it return NULL or another pointer, in which case pointer points to new object, but this way at some point the two objects exist at the same time which I don't like (they have big size) so I was looking for a way to delete the first object and then creating the next object. – TomTsagk Dec 11 '15 at 08:45
  • @TomTsagk, `struct` is just so that the sample code is not cluttered with `public:`. You should make it a `class` in a production code of course. For the memory usage problem, that's a different issue and my approach might not work in that case. – Petr Dec 11 '15 at 08:46
  • @TomTsagk, for big memory usage problem -- is your new `b` independent of original object? Or do you transfer a lot of data from original object to `b`? If latter, then you might want to have a `b(A&)` constructor that will just transfer the data and leave `A` in some valid, but 'empty' state. – Petr Dec 11 '15 at 09:05
  • @Petr It might transfer only minor data (like a couple of `int`s) but the object itself is big, I just want to avoid having both objects active at the same time. – TomTsagk Dec 11 '15 at 09:49
1

This code is valid ( for more information about correctness see this answer ).
But it's not a good practice, because other developer can miss nuance, that using one of member functions will lead to reconstruction of object.
It's better to explicitly reconstruct object than hide it in member function. Or just use smart pointers.

Community
  • 1
  • 1
Roman Zaitsev
  • 1,328
  • 5
  • 20
  • 28
1

As a design I don't like that a pointer suddenly points to another object (of a different type) when it is not clear that it happens. It could be argued that since OPs code passes &pointer it indicates that it may change. However, I prefer an assignment instead - I think that is more clear.

I would try something like this:

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;
}

The output is:

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
Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • This is similar to other answers suggesting smart pointers, I like the idea of assignment which as you said it's more clear, but by passing `&pointer` I avoid using smart pointers, do you suggest this way only for design purposes? So my code is safe, just not a good idea design-wise? Please note that class names (any variables) are simplified for the purpose of this question (I do not actually name my classes `A` and `a` :P). – TomTsagk Dec 11 '15 at 10:17
  • @TomTsagk - Originally I didn't intend to post answer at all. I liked the first answer posted by Petr. But due to a compile problem, Petr changed the answer quite a lot and I don't think the new answer is as good as the first. The answer made by alangab is a bit incomplete as it doesn't handle the case where you don't want the pointer to a new object instance. Therefore I decide to post this answer which is based on the original answer from Petr. (more in next comment...) – Support Ukraine Dec 11 '15 at 10:22
  • @TomTsagk - Regarding smart pointer: Don't think of it like `I avoid using smart pointers` ! Rather you should think of smart pointers as your friends. They help you keep track of allocated memory and makes sure the destructors are called as needed. Avoid raw-pointers when possible. – Support Ukraine Dec 11 '15 at 10:25
  • Sure smart pointers are useful, but I came to C++ from Java, and I got tired of java taking care of everything for me, I like that in C/C++ I have more power, which is both good and bad. I'm currently reading online about smart pointers (I found out about them today) I don't know if I'll be using them or not (yet). – TomTsagk Dec 11 '15 at 10:31
  • +1 -- the trick with `move` is something I was missing. In fact I did try it, but was not able to make it work. – Petr Dec 11 '15 at 12:21
0

In general, You must ensure that every execution path that call function doesn't have a stack frame for a method of A or derived class (this is invalid). So, it is dangerous. In MFC api programming, this happens "normally" in the WM_NCDESTROY message handler. In it you do thing like delete this, but Windows ensure that WM_NCDESTROY is the last message sent to a window.

I suggest you to change a little the api of class A and use unique_ptr to handle the memory:

#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;
}

In this way, you move the destruction from inside f() to the assignment of p.

alangab
  • 849
  • 5
  • 20
  • Thanks a lot for the answer, but smart pointers (with `unique_ptr`) was already suggested by another answer. – TomTsagk Dec 11 '15 at 08:47