TransWikia.com

How can I do memory management in C++ when a class needs to throw out and replace a member object many times during its lifetime?

Software Engineering Asked on December 26, 2021

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?

One Answer

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).

Answered by amon on December 26, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP