0

I wrote a small program, to check the difference between creating shared_ptr via new and make_shared() function in case of exceptions. I read everywhere that via make_shared() it is an exception-safe.

But the interesting thing about both these cases is that the destructor in both cases is not called after stack unwinding? Am I missed something? Thanks in advance.

#include <iostream>
#include <memory>

class Car
{
public:
    Car() { cout << "Car constructor!" << endl; throw std::runtime_error("Oops"); }
    ~Car() { cout << "Car destructor!" << endl; }
};

void doProcessing()
{
//    std::shared_ptr<Car> sp(new Car());
    std::shared_ptr<Car> sp2 = std::make_shared<Car>();
}

int main()
{
    try
    {
        doProcessing();
    }
    catch(...)
    {
    }
    return 0;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
Oleg
  • 1,027
  • 1
  • 8
  • 18

2 Answers2

12

What object?

The only object in a smart pointer here did not actually complete construction, because its constructor threw. It doesn't exist.

You don't need smart pointers to demonstrate this. Just throw from any constructor and you'll see that the destructor body is not invoked.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1

Just wanted to add an answer addressing "I read everywhere that via make_shared() it is an exception-safe" part of your question, the rest is already answered by Lightness Races in Orbit.

Difference between make_share and shared_ptr(new Car) can be demonstrated by below program.

class Car
{
public:
    Car() { cout << "Car constructor!" << endl; throw std::runtime_error("Car oops"); }
    ~Car() { cout << "Car destructor!" << endl; }
};

class Bycicle
{
public:
    Bycicle() { cout << "Bycicle constructor!, does not throw" << endl;}
    ~Bycicle() { cout << "Bycicle destructor!" << endl; }
};

void doProcessing(std::shared_ptr<Car> /*carPtr*/, std::shared_ptr<Bycicle> /*bPtr*/)
{

}

int main()
{
    try
    {
        doProcessing(std::shared_ptr<Car>(new Car), std::shared_ptr<Bycicle>(new Bycicle));
    }
    catch(std::exception& ex)
    {
        std::cout << "Ex is " << ex.what() << std::endl;
    }
    return 0;
}

Until C++17, compiler is allowed to make following function calls (in the order described)

-- Call new Bycicle along with the constructor of Bycicle but NOT call ctor of shared_ptr.

-- Call the constructor of Car which throws.

In this case, as pointed out Car was never fully constructed so it won't leak. However constructor of Bycicle was fully executed and it does leak (since shared_ptr does NOT yet own the object).

Calling doProcessing(std::make_shared<Car>(), std::make_shared<Bycicle>()); guarantees that ownership of fully allocated objects is passed to the shared_ptr.

Final Note: This is not applicable since C++ 17, because C++ 17 guarantees that arguments are evaluated fully (order in which they are evaluated is still not guaranteed).

David
  • 602
  • 5
  • 14