Исходное размещение на С++ New
Как выполнить новую операцию размещения с помощью летучего указателя.
Например, я хочу сделать что-то вроде этого:
volatile SomeStruct Object;
volatile SomeStruct* thing = &Object;
new (thing) SomeStruct(/*arguments to SomeStruct constructor*/);
Я знаю, что это сработает, если не будет ключевого слова volatile...... но как я могу это сделать с изменчивой переменной?
Примечание:
Размещение new определяется следующим образом:
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; }
Проблема в том, что я пытаюсь преобразовать thing
типа volatile SomeStruct*
в void*
, что недопустимо.
Например, если я изменил новый оператор на это:
void* operator new(size_t memoryRequested, volatile void* pointer)
{
return (void*)pointer;
}
Он будет компилироваться, но будет ссылаться на поведение undefined.
Ответы
Ответ 1
Я хочу сказать, что вы можете сделать это вот так:
new (const_cast<SomeStruct*>(thing)) volatile SomeStruct(...);
Но я не уверен, действительно ли это или нет. Проблема заключается в том, что, поскольку функция распределения возвращает void*
, в которую нужно построить объект volatile SomeStruct
, доступ к памяти может не иметь изменчивой семантики, приводящей к поведению undefined.
Итак, я не уверен, правильно ли использовать новое размещение для создания объекта в энергозависимом блоке памяти. Однако, если предположить, что память была первоначально, скажем, энергонезависимым массивом char
, это похоже на правильное решение.
Ответ 2
Я знаю, что это сработает, если не было ключевого слова volatile
...... но как это сделать с переменной volatile
?
Размещение new
связано с построением объекта в определенном месте. cv-квалификаторы применяются только после создания объекта. const
-ness или volatile
-ity применимы только после создания объекта. В этом смысле имеет смысл, что размещение new
не обеспечивает перегрузку, которая принимает указатель volatile
(или const
). Из стандарта С++ (черновик) [class.ctor/3] здесь;
Конструктор может быть вызван для объекта const
, volatile
или const volatile
. const
и volatile
семантика ([dcl.type.cv]) не применяются к строящемуся объекту. Они вступают в силу, когда заканчивается конструктор для наиболее производного объекта ([intro.object]).
Любая попытка отбросить volatile
приводит к поведению undefined, см. здесь cppreference;
Изменение объекта const
через путь доступа не const
и обращение к объекту volatile
с помощью не-t20 > glvalue приводит к поведению undefined.
См. также [expr.const.cast/6].
Учитывая использование 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
быть неустойчивым? volatile
части struct
могут быть интернализированы/абстрагированы, а cv-квалификаторы данных не должны быть подвержены клиенту для начала, он обрабатывается внутри struct
;
struct SomeStruct {
volatile int data;
void DoSomething()
{
data = 42;
}
};
SomeStruct Object;
/* ... */
void* p = &Object;
auto p2 = new (p) SomeStruct{};
p2->DoSomething();
Интернализировать инициализацию энергозависимого объекта, альтернативой является позволить SomeStruct
ленить инициализировать (или повторно инициализировать /reset) по мере необходимости. Учитывая некоторые из кажущихся ограничений, это может быть и нецелесообразно.
struct SomeStruct {
void Initialise() volatile
{
/*...*/
}
}
Ответ 3
Я думаю, что это может помочь вам в том, чего вы пытаетесь достичь. Теперь класс шаблона, который я вам показываю, написан с использованием платформы Windows для блокировки потоков, вы можете изменить этот класс для работы с другими платформами ОС по мере необходимости. Он просто используется в качестве иллюстрации того, как можно достичь вышеупомянутой семантики. Это компилирует, запускает и завершает работу с кодом 0 для Visual Studio 2015 CE. Этот класс полагается на заголовочный файл <Windows.h>
для использования CRITICAL_SECTION
, EnterCriticalSection()
, LeaveCriticalSection()
, InitializeCriticalSection()
и DeleteCriticalSection()
. Если есть альтернатива этим в других библиотеках, таких как библиотека ускорения, этот класс может быть легко написан для достижения той же функциональности. Этот класс предназначен для блокировки определяемого пользователем объекта класса как изменчивого при работе с несколькими потоками.
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"
Теперь вот основное запущенное приложение, которое использует шаблонный volatile locker class и использование нового оператора размещения.
#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 overloaded ->() operator.
std::cout << VolatileLocker<SomeClass>( *pSomeClass, s_criticalSection )->getValue() << std::endl;
// Placement New Operator On Our Pointer To Our Object Using The Class 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 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 SomeClass localStackObject( 2 );
SomeClass* pSomeClass = nullptr;
pSomeClass = &localStackObject; // Invalid - volatile SomeClass* cannot be assigned to an entity of type SomeClass*
Если вы попытаетесь обойти это, используя прямую локальную переменную напрямую, вы все равно можете использовать ее с помощью 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 для доступа к методам класса, поэтому localStackObject не может использоваться напрямую.
// This Is Invalid:
std::cout << localStackObject.getValue() << std::endl;
// Use This Instead:
std::cout << VolatileLocker<SomeClass>( localStackObject, s_criticalSection )->getValue() << std::endl;
В качестве важного напоминания этот класс был первоначально разработан с учетом конкретной платформы Windows, однако концепция этого класса шаблона может быть легко написана с использованием кросс-платформенной модульности, просто заменив CRITICAL_SECTION на любые доступные кросс- платформенные эквивалентные функции.
Вот справочный ответ для работы с системами на базе Linux/Unix:
fooobar.com/info/335134/...
Вот справочный ответ для работы с системами на базе Mac/Apple:
fooobar.com/info/335135/...
Вот ссылки для написания межплатформенных эквивалентов модульности: