The new operator in C++ performs the following actions:
Allocates Memory: It allocates memory on the heap for a single object or an array of objects. The amount of memory allocated is enough to hold the object(s) of the specified type.
Initializes Object(s): After allocating memory, new initializes the object(s) by calling their constructor(s). For a single object, it calls the constructor directly. For an array of objects, it calls the constructor for each object in the array.
Returns a Pointer: It returns a pointer of the object's type that points to the first byte of the allocated memory where the object is stored. For an array, it returns a pointer to the first element of the array.
Whether a pointer has been returned, but the constructor has not fully executed with out-of-order execution of CPU
class Singleton
{
private:
static Singleton * pinstance_;
static std::mutex mutex_;
protected:
Singleton()
{
//
}
~Singleton() {}
public:
Singleton(Singleton &other) = delete;
void operator=(const Singleton &) = delete;
static Singleton *GetInstance();
};
Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;
Singleton *Singleton::GetInstance()
{
if (pinstance_ == nullptr)
{
std::lock_guard<std::mutex> lock(mutex_);
if (pinstance_ == nullptr)
{
pinstance_ = new Singleton();
}
}
return pinstance_;
}
Thread A executes the new operator after getting the lock, and does not complete the constructor function, but the pointer has pointed to the requested memory. At this time, thread B comes in and finds that the pointer is not NULL in the first if judgment, and uses the pointer for subsequent processing
Is the above situation possible in the case of multithreading ?
If you write C++ code that has well-defined behavior according to the C++ standard, then you don't need to worry about the instructions being executed "out of order". In particular, the standard guarantees that in a new-expression, the allocation function will be called first, then, if the allocation is successful, the object will be initialized, and only after such initialization succeeds, the new-expression has a value and that value can be further used by the program. Thus, you can assume that your compiler will generate code that will make the program behave according to this sequence of events.
In multithreaded programs, the guarantees offered by the C++ standard are weaker. A data race in a multithreaded program causes the program to have undefined behaviour, and the observed behaviour of the program can make it seem like instructions are executed out of order, or even more unpredictable results can occur. Moreover, even in a multithreaded program that does not have data races, it is not guaranteed that when thread A observes a side effect caused by thread B, thread A can also see all previous side effects caused by thread B, unless the appropriate synchronization operations are used. Again, if you need particular behaviour, it is your responsibility to write code that gives that behaviour according to the C++ standard, using the synchronization operations provided by the standard. If you do that, you don't need to think about what is happening at the machine level.