简体   繁体   中英

C++ Volatile Placement New

How does one do a placement new operation on a volatile pointer.

For example, I want to do something like this:

volatile SomeStruct Object;
volatile SomeStruct* thing = &Object;
new (thing) SomeStruct(/*arguments to SomeStruct's constructor*/);

I know this would work if there was no volatile keyword......but how can I do this with a volatile variable?

Note:

Placement new is defined like this:

void* operator new(size_t memoryRequested, void* pointer)
{
  return pointer;
}

(By the way here is how GCC implements it):

// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

The problem is that I am trying to convert thing of type volatile SomeStruct* to void* , which is not allowed.

For example if I change the new operator to this:

void* operator new(size_t memoryRequested, volatile void* pointer)
{
  return (void*)pointer;
} 

It would compile, but would invoke undefined behavior.

I want to say you can do it like this:

new (const_cast<SomeStruct*>(thing)) volatile SomeStruct(...);

But I'm not actually sure whether this is valid or not. The problem is that since the allocation function returns a void* into which to construct the volatile SomeStruct object, accesses to the memory may not have volatile semantics, leading to undefined behavior.

So I'm not sure whether it's legal to use placement new to construct an object into a volatile-qualified block of memory. However, assuming the memory was originally, say, a non-volatile array of char , this seems like the correct solution.

I know this would work if there was no volatile keyword......but how can I do this with a volatile variable?

Placement new has to do with constructing an object at a given location. cv-qualifiers are only applied after the object is constructed. The const -ness or volatile -ity are only applicable once the object is constructed. In that sense, it makes sense that the placement new does not provide an overload that accepts a volatile (or const ) pointer. From the C++ standard (draft) [class.ctor/3] here ;

A constructor can be invoked for a const , volatile or const volatile object. const and volatile semantics ([dcl.type.cv]) are not applied on an object under construction. They come into effect when the constructor for the most derived object ([intro.object]) ends.

Any attempt to cast away the volatile leads to undefined behavior, see the cppreference here ;

Modifying a const object through a non- const access path and referring to a volatile object through a non- volatile glvalue results in undefined behavior.

See also [expr.const.cast/6] .

Given the use of the volatile and the placement new , the assertion in the question (and some of the comments) is that the object is required for use with a signal handler and maps to a specific location in memory.

There are some alternatives though...

If the specific location is not needed , the best is to not use the placement new and just add a volatile qualifer to the object wherever it is declared;

struct SomeStruct {
    /*...*/
};
// ...
volatile SomeStruct Object;

If both the placement new and the volatile is needed , then reorder their use. Construct the object as required and then add the qualifier;

SomeStruct Object;
// ...
void* p = &Object; // or at the required location
volatile SomeStruct* p2 = new (p) SomeStruct;

Does the struct have to be volatile? The volatile parts of the struct could be internalised/abstracted and the cv-qualifiers of the data would not need to be exposed to the client to begin with, it is dealt with internally to the struct ;

struct SomeStruct {
    volatile int data;
    void DoSomething()
    {
        data = 42;
    }
};

SomeStruct Object;
/* ... */
void* p = &Object;
auto p2 = new (p) SomeStruct{};
p2->DoSomething();

Internalise the initialise of the volatile object , an alternative is to allow the SomeStruct to lazy initialise (or re-initialise/reset) itself as needed. Given some of the apparent constraints, this may not be that feasible.

struct SomeStruct {
    void Initialise() volatile
    {
        /*...*/
    }
}

I think this may help you with what it is you are trying to achieve. Now the template class I'm showing you is written with use of the Windows Platform for locking threads, you can modify this class to work with other OS - Platforms as needed. It is just used as an illustration of how one could achieve the above semantics. This does compile, run and exits with a code of 0 for Visual Studio 2015 CE. This class does rely on the <Windows.h> header file for the use of CRITICAL_SECTION , EnterCriticalSection() , LeaveCriticalSection() , InitializeCriticalSection() & DeleteCriticalSection() . If there is an alternative to these with in other libraries such as the boost library this class can easily be written to achieve the same functionality. This class is designed to lock a user defined class object as volatile while working across multiple threads.

VolatileLocker.h

#ifndef VOLATILE_LOCKER_H
#define VOLATILE_LOCKER_H

#include <Windows.h>

template<typename T>
class VolatileLocker {
private:
    T*  m_pObject;
    CRITICAL_SECTION* m_pCriticalSection;

public:
    VolatileLocker( volatile T& objectToLock, CRITICAL_SECTION& criticalSection );
    ~VolatileLocker();

    T* operator->();

private:
    VolatileLocker( const VolatileLocker& c ); // Not Implemented
    VolatileLocker& operator=( const VolatileLocker& c ); // Not Implemented

}; // VolatileLocker

#include "VolatileLocker.inl"

#endif // VOLATILE_LOCKER_H

VolatileLocker.inl

// ----------------------------------------------------------------------------
// VolatileLocker()
// Locks A Volatile Variable So That It Can Be Used Across Multiple Threads Safely
template<typename T>
VolatileLocker<T>::VolatileLocker( volatile T& objectToLock, CRITICAL_SECTION& criticalSection ) :
    m_pObject( const_cast<T*>( &objectToLock ) ),
    m_pCriticalSection( &criticalSection ) {
    EnterCriticalSection( m_pCriticalSection );
} // VolatileLocker

// ----------------------------------------------------------------------------
// ~VolatileLocker()
template<typename T>
VolatileLocker<T>::~VolatileLocker() {
    LeaveCriticalSection( m_pCriticalSection );
} // ~VolatileLocker

// ----------------------------------------------------------------------------
// operator->()
// Allow The Locked Object To Be Used Like A Pointer
template <typename T>
T* VolatileLocker<T>::operator->() {
    return m_pObject;
} // operator->

VolatileLocker.cpp

#include "VolatileLocker.h"

Now here is the main running application that uses the templated volatile locker class and the use of the placement new operator.

#include <iostream>
#include "VolatileLocker.h"

static CRITICAL_SECTION s_criticalSection;

class SomeClass {
private:
    int m_value;

public:
    explicit SomeClass( int value ) : m_value( value ) {}

    int getValue() const { return m_value; }

}; // SomeClass

int main() {
    InitializeCriticalSection( &s_criticalSection ); // Initialize Our Static Critical Section

    SomeClass localStackObject( 2 ); // Create A Local Variable On The Stack And Initialize It To Some Value

    // Create A Pointer To That Class And Initialize It To Null.
    SomeClass* pSomeClass = nullptr;
    // Not Using Heap Here, Only Use Local Stack For Demonstration, So Just Get A Reference To The Stack Object
    pSomeClass = &localStackObject;

    // Here Is Our Pointer / Reference To Our Class As A Volatile Object 
    // Which Is Also Locked For Thread Safety Across Multiple Threads
    // And We Can Access The Objects Fields (public variables, methods) via
    // the VolatileLocker's overloaded ->() operator.
    std::cout << VolatileLocker<SomeClass>( *pSomeClass, s_criticalSection )->getValue() << std::endl;

    // Placement New Operator On Our Pointer To Our Object Using The Class's Constructor
    new (pSomeClass) SomeClass( 4 );

    // Again Using The Volatile Locker And Getting The New Value.
    std::cout << VolatileLocker<SomeClass>( *pSomeClass, s_criticalSection )->getValue() << std::endl;

    // Here Is The Interesting Part - Let's Check The Original Local Stack Object
    std::cout << localStackObject.getValue() << std::endl;

    // Cleaning Up Our Critical Section.
    DeleteCriticalSection( &s_criticalSection );
    return 0;
} // main

Output

2
4
4

NOTE:

Something to be aware of. The initial local stack variable itself is not volatile. If you try to declare the stack variable as volatile and used it directly as such:

volatile SomeClass localStackObject( 2 );
SomeClass* pSomeClass = nullptr;
pSomeClass = &localStackObject; // Invalid - volatile SomeClass* cannot be assigned to an entity of type SomeClass*

If you try to work around this by using the volatile local variable directly you can still use it with the VolatileLocker, but you won't be able to use the Placement New as this snippet shows:

std::cout << VolatileLocker<SomeClass>( localStackObject, s_criticalSection )->getValue() << std::endl; // Line Okay - Notice using object directly and no dereferencing.

// However when we get to this line of code here:
new (localStackObject) SomeClass( 4 ); // Does Not Compile. There Is No Instance Of Operator New To Match The Argument List

// To Fix That We Can Do This:
new ( const_cast<SomeClass*>( &localStackObject) ) SomeClass( 4 ); // This Will Compile

However to access any members using this design method you would then have to use the VolatileLocker to access the class's methods so the localStackObject can not be used directly .

// This Is Invalid:
std::cout << localStackObject.getValue() << std::endl; 

// Use This Instead:   
std::cout << VolatileLocker<SomeClass>( localStackObject, s_criticalSection )->getValue() << std::endl;

As an important reminder note this class was originally designed with the specific windows platform in mind, however, the concept of this template class can easily be written with cross-platform modularity in mind just by replacing the CRITICAL_SECTION with any available cross-platform equivalent functions.

Here is a reference answer for working with Linux / Unix based systems: stackoverflow/multithreading/linux

Here is a reference answer for working with Mac / Apple based systems: stackoverflow/multithreading/mac

Here are references to write cross-platform modularity equivalents:

  1. cppreference/thread
  2. cppreference/condition_variable

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM