C++ 易失性放置新
C++ Volatile Placement New
如何对易失性指针进行 placement new 操作。
例如,我想做这样的事情:
volatile SomeStruct Object;
volatile SomeStruct* thing = &Object;
new (thing) SomeStruct(/*arguments to SomeStruct's constructor*/);
我知道如果没有 volatile 关键字这会起作用......但是我如何使用 volatile 变量来做到这一点?
注:
新的展示位置定义如下:
void* operator new(size_t memoryRequested, void* pointer)
{
return pointer;
}
(顺便说一下GCC是如何实现的):
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
问题是我正在尝试将 volatile SomeStruct*
类型的 thing
转换为 void*
,这是不允许的。
例如,如果我将新运算符更改为:
void* operator new(size_t memoryRequested, volatile void* pointer)
{
return (void*)pointer;
}
它会编译,但会调用未定义的行为。
我想说你可以这样做:
new (const_cast<SomeStruct*>(thing)) volatile SomeStruct(...);
但我不确定这是否有效。问题在于,由于分配函数 returns 一个 void*
用于构造 volatile SomeStruct
对象,因此对内存的访问可能没有易失性语义,从而导致未定义的行为。
所以我不确定使用 placement new 将对象构造成 volatile 限定的内存块是否合法。但是,假设内存最初是 char
的非易失性数组,这似乎是正确的解决方案。
我认为这可以帮助您实现您想要实现的目标。现在,我向您展示的模板 class 是使用 Windows 锁定线程的平台编写的,您可以修改此 class 以与其他 OS - 平台一起使用需要。它只是用来说明如何实现上述语义。对于 Visual Studio 2015 CE,这会编译 运行 并以代码 0 退出。 class 确实依赖 <Windows.h>
头文件来使用 CRITICAL_SECTION
、EnterCriticalSection()
、LeaveCriticalSection()
、InitializeCriticalSection()
和 DeleteCriticalSection()
.如果在其他库(例如 boost 库)中有替代这些的方法,则可以轻松编写此 class 来实现相同的功能。此 class 旨在在跨多个线程工作时将用户定义的 class 对象锁定为易变对象。
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"
现在这里是主要的 运行ning 应用程序,它使用模板化易失性储物柜 class 和放置 new 运算符的使用。
#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
输出
2
4
4
注意:
需要注意的事项。初始局部堆栈变量本身不是易变的。如果您尝试将堆栈变量声明为 volatile 并直接使用它:
volatile SomeClass localStackObject( 2 );
SomeClass* pSomeClass = nullptr;
pSomeClass = &localStackObject; // Invalid - volatile SomeClass* cannot be assigned to an entity of type SomeClass*
如果您尝试通过直接使用 volatile 局部变量来解决此问题,您仍然可以将它与 VolatileLocker 一起使用,但您将无法使用 Placement New,如以下代码片段所示:
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
然而,要使用此设计方法访问任何成员,您必须使用 VolatileLocker 来访问 class 的方法,因此不能直接使用 localStackObject。
// This Is Invalid:
std::cout << localStackObject.getValue() << std::endl;
// Use This Instead:
std::cout << VolatileLocker<SomeClass>( localStackObject, s_criticalSection )->getValue() << std::endl;
请注意,这个 class 最初是为特定的 windows 平台设计的,但是,这个模板的概念 class 可以很容易地用交叉编写只需将 CRITICAL_SECTION 替换为任何可用的跨平台等效函数即可牢记平台模块化。
以下是使用 Linux / Unix 系统的参考答案:
Whosebug/multithreading/linux
这是使用 Mac / Apple 系统的参考答案:
Whosebug/multithreading/mac
以下是编写跨平台模块化等价物的参考资料:
I know this would work if there was no volatile
keyword......but how can I do this with a volatile
variable?
放置 new
与在给定位置构造对象有关。 cv-qualifiers 仅在构造对象后应用。 const
-ness 或 volatile
-ity 仅在构造对象后适用。从这个意义上说,放置 new
不提供接受 volatile
(或 const
)指针的重载是有道理的。来自 C++ 标准(草案)[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.
任何放弃 volatile
的尝试都会导致未定义的行为,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.
考虑到 volatile
的使用和放置 new
,问题中的断言(以及一些评论)是该对象需要与信号处理程序和映射一起使用到内存中的特定位置。
虽然还有一些选择...
如果不需要具体位置,最好不要使用位置new
,只要在对象的任何地方添加一个volatile
限定符已声明;
struct SomeStruct {
/*...*/
};
// ...
volatile SomeStruct Object;
如果放置 new
和 volatile
都需要,则重新排序它们的使用。根据需要构造对象,然后添加限定符;
SomeStruct Object;
// ...
void* p = &Object; // or at the required location
volatile SomeStruct* p2 = new (p) SomeStruct;
struct
必须是易变的吗? struct
的 volatile
部分可以是 internalised/abstracted 并且数据的 cv-qualifiers 一开始不需要暴露给客户端,它在内部处理到 struct
;
struct SomeStruct {
volatile int data;
void DoSomething()
{
data = 42;
}
};
SomeStruct Object;
/* ... */
void* p = &Object;
auto p2 = new (p) SomeStruct{};
p2->DoSomething();
内部化 volatile 对象的初始化,另一种方法是允许 SomeStruct
根据需要延迟初始化(或 re-initialise/reset)自身。考虑到一些明显的限制,这可能不太可行。
struct SomeStruct {
void Initialise() volatile
{
/*...*/
}
}
如何对易失性指针进行 placement new 操作。
例如,我想做这样的事情:
volatile SomeStruct Object;
volatile SomeStruct* thing = &Object;
new (thing) SomeStruct(/*arguments to SomeStruct's constructor*/);
我知道如果没有 volatile 关键字这会起作用......但是我如何使用 volatile 变量来做到这一点?
注:
新的展示位置定义如下:
void* operator new(size_t memoryRequested, void* pointer)
{
return pointer;
}
(顺便说一下GCC是如何实现的):
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
问题是我正在尝试将 volatile SomeStruct*
类型的 thing
转换为 void*
,这是不允许的。
例如,如果我将新运算符更改为:
void* operator new(size_t memoryRequested, volatile void* pointer)
{
return (void*)pointer;
}
它会编译,但会调用未定义的行为。
我想说你可以这样做:
new (const_cast<SomeStruct*>(thing)) volatile SomeStruct(...);
但我不确定这是否有效。问题在于,由于分配函数 returns 一个 void*
用于构造 volatile SomeStruct
对象,因此对内存的访问可能没有易失性语义,从而导致未定义的行为。
所以我不确定使用 placement new 将对象构造成 volatile 限定的内存块是否合法。但是,假设内存最初是 char
的非易失性数组,这似乎是正确的解决方案。
我认为这可以帮助您实现您想要实现的目标。现在,我向您展示的模板 class 是使用 Windows 锁定线程的平台编写的,您可以修改此 class 以与其他 OS - 平台一起使用需要。它只是用来说明如何实现上述语义。对于 Visual Studio 2015 CE,这会编译 运行 并以代码 0 退出。 class 确实依赖 <Windows.h>
头文件来使用 CRITICAL_SECTION
、EnterCriticalSection()
、LeaveCriticalSection()
、InitializeCriticalSection()
和 DeleteCriticalSection()
.如果在其他库(例如 boost 库)中有替代这些的方法,则可以轻松编写此 class 来实现相同的功能。此 class 旨在在跨多个线程工作时将用户定义的 class 对象锁定为易变对象。
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"
现在这里是主要的 运行ning 应用程序,它使用模板化易失性储物柜 class 和放置 new 运算符的使用。
#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
输出
2
4
4
注意:
需要注意的事项。初始局部堆栈变量本身不是易变的。如果您尝试将堆栈变量声明为 volatile 并直接使用它:
volatile SomeClass localStackObject( 2 );
SomeClass* pSomeClass = nullptr;
pSomeClass = &localStackObject; // Invalid - volatile SomeClass* cannot be assigned to an entity of type SomeClass*
如果您尝试通过直接使用 volatile 局部变量来解决此问题,您仍然可以将它与 VolatileLocker 一起使用,但您将无法使用 Placement New,如以下代码片段所示:
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
然而,要使用此设计方法访问任何成员,您必须使用 VolatileLocker 来访问 class 的方法,因此不能直接使用 localStackObject。
// This Is Invalid:
std::cout << localStackObject.getValue() << std::endl;
// Use This Instead:
std::cout << VolatileLocker<SomeClass>( localStackObject, s_criticalSection )->getValue() << std::endl;
请注意,这个 class 最初是为特定的 windows 平台设计的,但是,这个模板的概念 class 可以很容易地用交叉编写只需将 CRITICAL_SECTION 替换为任何可用的跨平台等效函数即可牢记平台模块化。
以下是使用 Linux / Unix 系统的参考答案: Whosebug/multithreading/linux
这是使用 Mac / Apple 系统的参考答案: Whosebug/multithreading/mac
以下是编写跨平台模块化等价物的参考资料:
I know this would work if there was no
volatile
keyword......but how can I do this with avolatile
variable?
放置 new
与在给定位置构造对象有关。 cv-qualifiers 仅在构造对象后应用。 const
-ness 或 volatile
-ity 仅在构造对象后适用。从这个意义上说,放置 new
不提供接受 volatile
(或 const
)指针的重载是有道理的。来自 C++ 标准(草案)[class.ctor/3] here;
A constructor can be invoked for a
const
,volatile
orconst volatile
object.const
andvolatile
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.
任何放弃 volatile
的尝试都会导致未定义的行为,see the cppreference here;
Modifying a
const
object through a non-const
access path and referring to avolatile
object through a non-volatile
glvalue results in undefined behavior.
考虑到 volatile
的使用和放置 new
,问题中的断言(以及一些评论)是该对象需要与信号处理程序和映射一起使用到内存中的特定位置。
虽然还有一些选择...
如果不需要具体位置,最好不要使用位置new
,只要在对象的任何地方添加一个volatile
限定符已声明;
struct SomeStruct {
/*...*/
};
// ...
volatile SomeStruct Object;
如果放置 new
和 volatile
都需要,则重新排序它们的使用。根据需要构造对象,然后添加限定符;
SomeStruct Object;
// ...
void* p = &Object; // or at the required location
volatile SomeStruct* p2 = new (p) SomeStruct;
struct
必须是易变的吗? struct
的 volatile
部分可以是 internalised/abstracted 并且数据的 cv-qualifiers 一开始不需要暴露给客户端,它在内部处理到 struct
;
struct SomeStruct {
volatile int data;
void DoSomething()
{
data = 42;
}
};
SomeStruct Object;
/* ... */
void* p = &Object;
auto p2 = new (p) SomeStruct{};
p2->DoSomething();
内部化 volatile 对象的初始化,另一种方法是允许 SomeStruct
根据需要延迟初始化(或 re-initialise/reset)自身。考虑到一些明显的限制,这可能不太可行。
struct SomeStruct {
void Initialise() volatile
{
/*...*/
}
}