Исходное размещение на С++ 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/...

Вот ссылки для написания межплатформенных эквивалентов модульности: