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 variablevolatile
?
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 objetoconst
,volatile
oconst volatile
.const
semánticaconst
yvolatile
([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 noconst
y la referencia a un objetovolatile
través de un glvalue novolatile
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:
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.