-1

I'm seeing a strange behavior in one of my projects. The circumstances are as follows:

  • I have an object. Let's call Victim, which contains a pointer variable, a constructor and destructor.
  • I have another object, let's call Perpetrator, whose constructor accepts a Victim object, and copies the pointer variable to a concrete variable inside.
  • I create a Victim*, and create the object with new, then supply this to Perpetrator via Perpetrator(*victim).
  • When the Perpetrator's constructor finishes, Victim's destructor is called and object is deleted.

The problem is the only copy of the Victim, which is poor is completely destroyed during the construction process. Tidying up the program at the end via delete poor will cause a double-free error.

The behavior is consistent in C++98/11, GCC 4.8.5, 7.x, and LLVM CLANG, hence it must be well defined. What is this behavior is called, and what are the semantics of it?

My theory is, since the constructor accepts a concrete object, it's considered copied, so it's destructed when the constructor / function is done.

Since I praise PoC||GTFO, here's the code:

Clarification: The example is written intentionally, since it's a simplified model of a much more complex, but non-leaky and well-managed data structure complex. Removing all necessary bits made it look like an horribly broken code. In the real code Victim is a long living data store and Perpetrator is an interim storage variable used for processing said data.

#include <iostream>

using namespace std;

struct Victim
{
  double* pointer;

  Victim ()
  {
    this->pointer = new double(42.2);
  }

  ~Victim ()
  {
    cout << "Destructor of the Victim is called." << endl;
    delete this->pointer;
  }

};

struct Perpetrator
{
  double concrete;

  Perpetrator (Victim victim)
  {
    concrete = *(victim.pointer);
  }
};


int main ()
{
  Victim* poor = new Victim();
  Perpetrator cruel(*poor);

  cout << cruel.concrete << endl;
}

Sample output:

./destructor_test 
Destructor of the Victim is called.
42.2
bayindirh
  • 425
  • 6
  • 21
  • 3
    Rule of 0/3/5 broken. `double* pointer;` should probably simply be `double value;`, so no memory management. – Jarod42 Dec 12 '17 at 10:01
  • The example is written intentionally in this way. I wanted to show that a benign copy can destruct the object which may be planned to be used for much longer. – bayindirh Dec 12 '17 at 10:18

3 Answers3

4

Perpetrator (Victim victim) - is passing an object by value. Means it has to be (copy) constructed, and then destructed when the call finishes.

As a matter of fact, there is no need to use new at all. This:

int main ()
{
  Victim poor;
  Perpetrator cruel(poor);

  cout << cruel.concrete << endl;
}

Acts similarly. You'll see two constructions, and two destructions. The second one your original code doesn't exhibit, because your example leaks.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I knowingly modeled the example to be leaky. You cannot re-delete the Victim in my example, since it's already and completely deleted. So it doesn't leak in practice, but leaves a trail of destruction behind. So Victim doesn't duplicated in the example, which it's duplicated in a vanilla "copy by value" scenario. – bayindirh Dec 12 '17 at 09:54
  • @bayindirh - *"since it's already and completely deleted"* No it is not. – StoryTeller - Unslander Monica Dec 12 '17 at 09:55
  • Yes it's. You'll get a double free error if you add a `delete poor` at the end. The only copy of the `Victim`, which is `poor` is destructed during the construction. – bayindirh Dec 12 '17 at 09:56
  • 4
    @bayindirh - The double delete error is because you double delete `this->pointer`. The `Victim` object itself is very much leaking. – StoryTeller - Unslander Monica Dec 12 '17 at 09:58
  • @bayindirh You might want to try adding copy constructor to `victim` and then do `delete poor` at end and see the results. – Gaurav Sehgal Dec 12 '17 at 10:04
  • @StoryTeller Accessing the `poor`'s double pointer after the construction will cause an read-after-free error. Actually, I modeled the code from another codebase to make it simpler, so the behavior, pointers and structures are all deliberate. I just wanted to learn whether C++'s non-discerning nature of incoming variables are expected or not. However, your answer have answered my question, thanks. – bayindirh Dec 12 '17 at 10:09
0

pass Victim by ref:

struct Perpetrator
{
    double concrete;

    Perpetrator (Victim& victim)
    {
        concrete = *(victim.pointer);
    }
};

Victim will construct & destruct once each.

0

Is this what you want to achive?

using namespace std;

struct Victim
{
  double* pointer;

  Victim ()
  {
    this->pointer = new double(42.2);
  }

  ~Victim ()
  {
    cout << "Destructor of the Victim is called." << endl;
    delete this->pointer;
  }
};

struct Perpetrator
{
  double concrete;

  Perpetrator (Victim *victim)
  {
    concrete = *(victim->pointer);
  }

    ~Perpetrator ()
  {
    cout << "Destructor of the Perpetrator is called." << endl;
  }
};


int main ()
{
  Victim* poor = new Victim();
  Perpetrator cruel(poor);
  cout << cruel.concrete << endl;
  delete poor;
}

output: 42.2
Destructor of the Victim is called.
Destructor of the Perpetrator is called.

RaduS
  • 1
  • 1