1

I'm writing a C++ application. This is a memory management question, I have a background in scripting languages and am new to C++.

I have a little class representing a GUI view, call it View, and within that view there's a text field and a button. When the user clicks the button, the text field is interpreted as a shell command and the shell command is run. This is managed by a Process class which has methods like run(), getOutput() and stop(). So View contains a reference of some sort to an instance of Process.

Initially my implementation looked like this (not actual code, just a sketch):

class View
{
    Process *process;
public:
    onClickExecute()
    {
        process = new Process(command);
        process->run();
    }

    onClickStop()
    {
        process->stop();
    }

    // Called when the process ends, either by the user clicking stop or just by it ending naturally.
    onProcessComplete()
    {
        delete process;
    }

}

This worked well, but then I thought: why am I torturing myself by making process a pointer? If I just make it by-value, I don't have to worry about deleting it later. This seemed like the more "correct" way to manage memory in this case, so I tried making process by value, but then C++ complained that I wasn't initializing it in the constructor! Process has no default constructor because a Process instance with no command is meaningless. I could make it meaningful by providing a dummy constructor and a setter, but that feels like I'm modifying my design just because of language technicalities rather than because it's the design I want. Is there some way I can tell C++ "just fill process with garbage uninitialized bytes until I click the button and create an instance"?

The more abstract problem here is: a parent object has a child object. The child object is meaningless when the parent is constructed, it becomes meaningful only later. The child object will need to be tossed and replaced with a "new" instance several times during the lifetime of the parent. How can we manage the memory here in the simplest way possible?

1
  • Try std::optional
    – user253751
    Jul 24, 2020 at 11:16

1 Answer 1

6

You do want to use pointers because you want to represent an object that can be present or absent, depending on the enclosing object's state. But dealing with raw pointers correctly is very tricky: Raw pointers are prone to bugs, segfaults, use-after-free vulnerabilities, memory leaks, and frequently break if exceptions are involved.

The solution is to use smart pointers. These are generally safe to use because the referenced object is automatically deleted when the smart pointer is destroyed, but can still model pointery things like null pointers.

There is std::optional which can be used to represent values that are present or absent without the overhead of allocating the pointed-to value separately, but it's not necessarily most appropriate here.

Instead: use std::unique_ptr. Whenever you think you need to use a pointer, first try using std::unique_ptr. Being an unique pointer means that the pointer owns the pointed-to object which allows it to be deleted with zero overhead compared to manual new/delete. If you would have a more complex object graph, then std::shared_ptr implements reference-counted pointers.

With an unique pointer, your code would look something like this:

class View {
  std::unique_ptr<Process> process;  // is initialized to nullptr
public:
  void onClickExecute() {
    if (process) return;  // what if process is already initialized?
    process = std::make_unique<Process>(command);
    process->run();
  }

  void onClickStop() {
    if (process) process->stop();
  }

  void onProcessComplete() {
    process.reset(nullptr); // will correctly delete if necessary
  }
};

Unique pointers became available with C++11, make_unique in C++14 (but it's just a few lines of code to backport it if necessary).

11
  • Just seems odd that I need to change the way things are laid out in memory just because I want to model a field that may or may not be present. It seems like this couples together two things that should be orthogonal. Why couldn't there just be a way of saying "store these bytes where the class instance is stored, but leave them uninitialized until I come in and initialize them"?
    – Jack M
    Jul 24, 2020 at 10:57
  • 2
    @JackM That's exactly what std::optional does, though it needs an extra bool to know whether the memory is currently initialized or not. If you want to do C-style programming without any safety checks, consider using a union with a single member.
    – amon
    Jul 24, 2020 at 10:58
  • 1
    If you don't have std::optional or boost::optional, you can do it yourself with std::aligned_storage_t<sizeof(Process), alignof(Process)> storage; Process * process; and process = new (storage) Process(command);, process->~Process(); process = nullptr;
    – Caleth
    Jul 24, 2020 at 11:05
  • 1
    @NicolBolas do you mean the default ~View()? Yes, you'd need to define View's special members (carefully)
    – Caleth
    Jul 24, 2020 at 13:46
  • 1
    @Caleth That’s true. But std::optional hides all those nitty gritty low-level details, and that’s the point. There are a lot of simple options available. At least: smart pointered heap allocation like in amon’s answer, one of the optional types, making Process default constructible. Because of that I wouldn’t consider manual usage of aligned_storage as a viable solution unless there are rock solid reasons why none of the alternatives is useable here.
    – besc
    Jul 25, 2020 at 12:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.