3

I have a union-like class with a member that may or may not be garbage depending on a boolean flag also set in that same class.

Obviously, I do not want that garbage to be destructed when my class goes out of scope. How to prevent a class member from being destructed?

I know this could be achieved with pointers and dynamically allocated memory, but I am looking for a more simple solution.

class MyContainer {
    bool has_child;
    MyChild child;

public:
    MyContainer(MyChild child) { this->has_child = true; this->child = child; }
    MyContainer() { this->has_child = false; }

    ~MyContainer() {
        if (!this->has_child) {
            // What to do here?
        }
    }
}
Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • just make destructor do nothing if it is on the stack. – Incomputable Feb 25 '17 at 13:55
  • 6
    You cannot prevent a class from being destroyed when it goes out of scope. This is fundamental to C++. Your problem is your overall class design, and not the destructor. – Sam Varshavchik Feb 25 '17 at 13:55
  • @Incomputable That will destruct `child` though, will it not? – Jeroen Feb 25 '17 at 13:56
  • @SamVarshavchik Is dynamically allocated memory the only way to go then? – Jeroen Feb 25 '17 at 13:56
  • 1
    The solution is to make sure that the member can be safely destroyed even if it's "garbage". – molbdnilo Feb 25 '17 at 13:57
  • @JeroenBollen, yes. So you will need to handle that problem as well. But at least it will prevent stack corruption :) – Incomputable Feb 25 '17 at 13:57
  • The `child` will be default constructed anyway, so it is not just "garbage". – Bo Persson Feb 25 '17 at 13:57
  • 1
    Well, that's one option. The other option, I suspect is to make your classes [compliant with the Rule Of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three), which I suspect is your real problem. If your class is compliant with the Rule Of Three, it doesn't matter whether it's on stack, or dynamically allocated. – Sam Varshavchik Feb 25 '17 at 13:58
  • @SamVarshavchik I suppose the question was badly worded. It doesn't matter if the container is on the stack, for there to be an issue here. I suppose the real issue is that child is not behind a pointer. – Jeroen Feb 25 '17 at 13:59
  • You should seek to get rid of the `MyContainer()` constructor. It apparently permits construction of semantically invalid objects. – Christian Hackl Feb 25 '17 at 14:21
  • @ChristianHackl Very funny and all, but that doesn't actually provide a solution. – Jeroen Feb 25 '17 at 14:22
  • @JeroenBollen: Why? It is the long-term solution. This entire class design is highly defective, and allowing the construction of invalid instances seems to be the root of all problems. – Christian Hackl Feb 25 '17 at 14:34
  • @ChristianHackl The code works just as bad with the class as without it. Getting rid of it might be part of a solution, but it certainly isn't one. – Jeroen Feb 25 '17 at 14:36

2 Answers2

5

You can use std::optional (since C++17), which won't cause dynamic memory allocation. e.g.

class MyContainer {
    bool has_child;
    std::optional<MyChild> child;

public:
    MyContainer(MyChild child) : child(child) { this->has_child = true; }
    MyContainer() { this->has_child = false; }

    ~MyContainer() { /* nothing special need to do */ }
};

BTW: The member has_child could be replaced by std::optional::has_value().

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Is there anything similar < C++17? – Jeroen Feb 25 '17 at 14:00
  • 1
    @JeroenBollen [Boost.Optional](http://www.boost.org/doc/libs/1_63_0/libs/optional/doc/html/index.html)? I'm not familiar with Boost, you might check the document carefully. – songyuanyao Feb 25 '17 at 14:05
  • I settled with `std::unique_ptr`. I'll be happy to switch to `std::optional` once C++17 is finished though. – Jeroen Feb 25 '17 at 14:23
  • 2
    @JeroenBollen: If you can freely choose your compiler, then you can easily become an early adopter of C++17 features if you really want to. For example, Visual C++ 2017 supports `std::optional` and many other C++17 features with the `/std:c++latest` flag. – Christian Hackl Feb 25 '17 at 14:41
2

I'm not that sure I would recommend it, but you can use placement new in a correctly sized array to do that.
As a minimal, working example:

#include<iostream>

struct MyChild {
    ~MyChild() { std::cout << "destructor" << std::endl; }
};

class MyContainer {
    bool drop;
    char arr[sizeof(MyChild)];
    MyChild *child;

public:
    MyContainer(bool drop)
        : drop{drop}, child{::new(&arr) MyChild{}}
    {}

    ~MyContainer() {
        if (drop) { child->~MyChild(); }
    }
};

void f() {
    std::cout << "f" << std::endl;
    MyContainer cont{true};
}

void g() {
    std::cout << "g" << std::endl;
    MyContainer cont{false};
}

int main() {
    f();
    g();
}

As you can see, destructor of MyChild is called only within f. Within MyContainer, whenever you want to access the instance of MyChild, you can do that through the child data member. And, of course, it's not dynamically allocated.

skypjack
  • 49,335
  • 19
  • 95
  • 187