qualifier new c++ volatile placement-new

new - C++ Colocación Volátil Nuevo



new c++ (3)

Sé que esto funcionaría si no hubiera una palabra clave volatile ... pero, ¿cómo puedo hacer esto con una variable volatile ?

La new ubicación tiene que ver con la construcción de un objeto en una ubicación determinada. Los calificadores cv solo se aplican después de que se construye el objeto. La constabilidad o la volatile solo son aplicables una vez que se construye el objeto. En ese sentido, tiene sentido que la ubicación new no proporcione una sobrecarga que acepte un puntero volatile (o const ). Desde el estándar de C ++ (borrador) [class.ctor / 3] aquí ;

Se puede invocar un const para un objeto const , volatile o const volatile . const semántica const y volatile ([dcl.type.cv]) no se aplica a un objeto en construcción. Se activan cuando finaliza el constructor para el objeto más derivado ([intro.object]).

Cualquier intento de desechar los conductores volatile hacia un comportamiento indefinido, vea la referencia de cpp aquí ;

La modificación de un objeto const través de una ruta de acceso no const y la referencia a un objeto volatile través de un glvalue no volatile dan como resultado un comportamiento indefinido.

Véase también [expr.const.cast/6] .

Dado el uso de la volatile y la new ubicación, la afirmación en la pregunta (y algunos de los comentarios) es que el objeto se requiere para usar con un controlador de señal y se asigna a una ubicación específica en la memoria.

Hay algunas alternativas aunque ...

Si la ubicación específica no es necesaria , lo mejor es no usar la ubicación new y simplemente agregar un calificador volatile al objeto donde se declara;

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

Si se necesita tanto la ubicación new como la volatile , entonces reordene su uso. Construya el objeto según sea necesario y luego agregue el calificador;

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

¿La struct tiene que ser volátil? Las partes volatile de la struct podrían internalizar / abstraer y los calificadores cv de los datos no necesitarían ser expuestos al cliente para comenzar, se trata internamente con la struct ;

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

Internalizar la inicialización del objeto volátil , una alternativa es permitir que SomeStruct perezosamente (o reinicialice / reinicie) a sí mismo según sea necesario. Dadas algunas de las restricciones aparentes, esto puede no ser tan factible.

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

¿Cómo se hace una nueva operación de colocación en un puntero volátil?

Por ejemplo, quiero hacer algo como esto:

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

Sé que esto funcionaría si no hubiera una palabra clave volátil ... pero, ¿cómo puedo hacer esto con una variable volátil?

Nota:

Colocación nueva se define así:

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

(Por cierto aquí es cómo GCC lo implementa):

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

El problema es que estoy tratando de convertir una thing del tipo volatile SomeStruct* en void* , lo cual no está permitido.

Por ejemplo si cambio el nuevo operador a esto:

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

Se compilaría, pero invocaría un comportamiento indefinido.


Creo que esto puede ayudarte con lo que estás tratando de lograr. Ahora, la clase de plantilla que estoy mostrando está escrita con el uso de la Plataforma de Windows para bloquear subprocesos, puede modificar esta clase para que funcione con otras plataformas de SO, según sea necesario. Solo se utiliza como ilustración de cómo se podría lograr la semántica anterior. Esto compila, ejecuta y sale con un código de 0 para Visual Studio 2015 CE. Esta clase se basa en el archivo de encabezado <Windows.h> para el uso de CRITICAL_SECTION , EnterCriticalSection() , LeaveCriticalSection() , InitializeCriticalSection() y DeleteCriticalSection() . Si existe una alternativa a estos en otras bibliotecas, como la biblioteca de impulso, esta clase puede escribirse fácilmente para lograr la misma funcionalidad. Esta clase está diseñada para bloquear un objeto de clase definido por el usuario como volátil mientras se trabaja en varios subprocesos.

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"

Ahora aquí está la aplicación principal que utiliza la clase de casilleros volátiles con plantilla y el uso del nuevo operador de ubicación.

#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

Salida

2 4 4

NOTA:

Algo a tener en cuenta. La variable de la pila local inicial en sí no es volátil. Si intenta declarar la variable de pila como volátil y la usa directamente como tal:

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

Si intenta evitar esto utilizando la variable local volátil directamente, aún puede usarlo con VolatileLocker, pero no podrá usar la Colocación nueva como muestra este fragmento:

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

Sin embargo, para acceder a cualquier miembro que use este método de diseño, entonces tendría que usar VolatileLocker para acceder a los métodos de la clase para que el localStackObject no se pueda usar directamente.

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

Como un recordatorio importante, esta clase se diseñó originalmente con la plataforma específica de Windows en mente, sin embargo, el concepto de esta clase de plantilla se puede escribir fácilmente con la modularidad multiplataforma en mente simplemente reemplazando CRITICAL_SECTION con cualquier función equivalente multiplataforma disponible .

Aquí hay una respuesta de referencia para trabajar con sistemas basados ​​en Linux / Unix: /multithreading/linux

Aquí hay una respuesta de referencia para trabajar con sistemas basados ​​en Mac / Apple: /multithreading/mac

Aquí hay referencias para escribir equivalentes de modularidad multiplataforma:

  1. cppreference/thread
  2. cppreference/condition_variable

Quiero decir que puedes hacerlo así:

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

Pero no estoy realmente seguro de si esto es válido o no. El problema es que, dado que la función de asignación devuelve un void* en el cual se construye el objeto volatile SomeStruct , es posible que los accesos a la memoria no tengan una semántica volátil, lo que lleva a un comportamiento indefinido.

Por lo tanto, no estoy seguro de si es legal usar una ubicación nueva para construir un objeto en un bloque de memoria calificado como volátil. Sin embargo, suponiendo que la memoria fuera originalmente, por ejemplo, una matriz no volátil de char , esta parece ser la solución correcta.