Переадресация в конструктор на месте

У меня есть класс сообщений, с которым раньше было немного сложно работать, вам нужно было создать класс сообщений, указать ему выделить пространство для вашего объекта, а затем заполнить пространство либо путем построения, либо по элементам.

Я хочу сделать возможным создание объекта сообщения с немедленным встроенным новообразованием результирующего объекта, но сделать это с помощью простого синтаксиса на месте вызова, гарантируя удаление копии.

#include <cstdint>

typedef uint8_t id_t;
enum class MessageID { WorldPeace };

class Message
{
    uint8_t* m_data;         // current memory
    uint8_t m_localData[64]; // upto 64 bytes.
    id_t m_messageId;
    size_t m_size; // amount of data used
    size_t m_capacity; // amount of space available
    // ...

public:
    Message(size_t requestSize, id_t messageId)
        : m_data(m_localData)
        , m_messageId(messageId)
        , m_size(0), m_capacity(sizeof(m_localData))
    {
        grow(requestSize);
    }

    void grow(size_t newSize)
    {
        if (newSize > m_capacity)
        {
            m_data = realloc((m_data == m_localData) ? nullptr : m_data, newSize);
            assert(m_data != nullptr); // my system uses less brutal mem mgmt
            m_size = newSize;
        }
    }

    template<typename T>
    T* allocatePtr()
    {
        size_t offset = size;
        grow(offset + sizeof(T));
        return (T*)(m_data + offset);
    }

#ifdef USE_CPP11
    template<typename T, typename Args...>
    Message(id_t messageId, Args&&... args)
        : Message(sizeof(T), messageID)
    {
        // we know m_data points to a large enough buffer
        new ((T*)m_data) T (std::forward<Args>(args)...);
    }
#endif
};

До C++11 у меня был неприятный макрос CONSTRUCT_IN_PLACE, который делал:

#define CONSTRUCT_IN_PLACE(Message, Typename, ...) \
    new ((Message).allocatePtr<Typename>()) Typename (__VA_ARGS__)

И вы бы сказали:

Message outgoing(sizeof(MyStruct), MessageID::WorldPeace);
CONSTRUCT_IN_PLACE(outgoing, MyStruct, wpArg1, wpArg2, wpArg3);

С С++ 11 вы должны использовать

Message outgoing<MyStruct>(MessageID::WorldPeace, wpArg1, wpArg2, wpArg3);

Но я считаю это грязным. Что я хочу реализовать:

    template<typename T>
    Message(id_t messageId, T&& src)
        : Message(sizeof(T), messageID)
    {
        // we know m_data points to a large enough buffer
        new ((T*)m_data) T (src);
    }

Чтобы пользователь использовал

Message outgoing(MessageID::WorldPeace, MyStruct(wpArg1, wpArg2, wpArg3));

Но кажется, что это сначала создает временный MyStruct в стеке, превращая new на месте в вызов конструктора перемещения T.

Многие из этих сообщений простые, часто POD, и они часто находятся в функциях сортировки, подобных этой:

void dispatchWorldPeace(int wpArg1, int wpArg2, int wpArg3)
{
    Message outgoing(MessageID::WorldPeace, MyStruct(wpArg1, wpArg2, wpArg3));
    outgoing.send(g_listener);
}

Поэтому я хочу избежать создания промежуточного временного файла, который потребует последующего перемещения/копирования.

Похоже, что компилятор должен иметь возможность исключить временное, а также перемещать и продвигать конструкцию вплоть до new на месте.

Что я делаю, из-за чего этого не происходит? (GCC 4.8.1, Clang 3.5, MSVC 2013)


person kfsone    schedule 31.01.2014    source источник
comment
Message w(id, in_place<MyStruct>(a,b,c)); делать? Да и std::forward<T>(src) в new не помешало бы.   -  person Yakk - Adam Nevraumont    schedule 01.02.2014
comment
Может быть, я посмотрю на это.   -  person kfsone    schedule 01.02.2014
comment
О, это может быть пропавший нападающий.   -  person kfsone    schedule 01.02.2014
comment
Я могу ошибаться, но разве это не должно быть template<typename T, typename... Args> Message(id_t messageId, Args&&... args)? По-другому не компилируется.   -  person    schedule 01.02.2014
comment
@remyabel К сожалению, это должно быть исправлено.   -  person kfsone    schedule 01.02.2014
comment
@Yakk Когда я заставляю его использовать std::forward, он больше не разрешает Message(id, *this);, потому что он не разрешает [неконстантный] ссылочный тип.   -  person kfsone    schedule 01.02.2014
comment
@kfsone Почему бы и нет? Я не понимаю, почему это не удается. Компилятор? Сообщение об ошибке? T?   -  person Yakk - Adam Nevraumont    schedule 01.02.2014
comment
Подождите, класс или конструктор templateed?   -  person Yakk - Adam Nevraumont    schedule 01.02.2014
comment
@Yakk У меня есть варианты использования uint32_t maxID; /*...*/ Message outgoing(MessageID::X, maxID);, Message outgoing(MessageID::Y, *this); и Message outgoing(MessageID::Z, SomeStruct(args);. Проблема заключалась в том, что я указывал T&& src в аргументах Message::Message.   -  person kfsone    schedule 01.02.2014


Ответы (2)


Вы не сможете исключить копирование/перемещение в новом размещении: исключение копирования полностью основано на идее, что компилятор знает во время построения, где в конечном итоге окажется объект. Кроме того, поскольку исключение копирования фактически изменяет поведение программы (в конце концов, она не будет вызывать соответствующий конструктор и деструктор, даже если они имеют побочные эффекты), исключение копирования ограничено несколькими очень специфическими случаями (перечисленными в 12.8 [ class.copy] параграф 31: по существу, при возврате локальной переменной по имени, при выбрасывании локальной переменной по имени, при перехвате исключения правильного типа по значению и при копировании/перемещении временной переменной; точные подробности см. в пункте ). Поскольку [размещение] new не является ни одним из контекстов, в которых можно исключить копию, а аргумент конструктора явно не временный (он именованный), копирование/перемещение никогда не будет исключено. Даже добавление отсутствующего std::forward<T>(...) в ваш конструктор приведет к исключению копирования/перемещения:

template<typename T>
Message(id_t messageId, T&& src)
    : Message(sizeof(T), messageID)
{
    // placement new take a void* anyway, i.e., no need to cast
    new (m_data) T (std::forward<T>(src));
}

Я не думаю, что вы можете явно указать параметр шаблона при вызове конструктора. Таким образом, я думаю, что самое близкое, что вы, вероятно, могли бы получить, не создавая объект заранее и не копируя/перемещая его, выглядит примерно так:

template <typename>
struct Tag {};

template <typename T, typename A>
Message::Message(Tag<T>, id_t messageId, A... args)
    : Message(messageId, sizeof(T)) {
    new(this->m_data) T(std::forward<A>(args)...);
}

Один из подходов, который может немного улучшить ситуацию, заключается в использовании id_t для сопоставления с соответствующим типом, предполагая, что существует сопоставление идентификаторов сообщений с соответствующим типом:

typedef uint8_t id_t;
template <typename T, id_t id> struct Tag {};
struct MessageId {
    static constexpr Tag<MyStruct, 1> WorldPeace;
    // ...
};
template <typename T, id_t id, typename... A>
Message::Message(Tag<T, id>, A&&... args)
    Message(id, sizeof(T)) {
    new(this->m_data) T(std::forward<A>)(args)...);
}
person Dietmar Kühl    schedule 01.02.2014

Предисловие

Концептуальный барьер, который не может преодолеть даже C++2049, заключается в том, что вам требуется, чтобы все биты, из которых состоит ваше сообщение, были выровнены в непрерывном блоке памяти.

Единственный способ, которым C++ может дать вам это, — использование оператора placement new. В противном случае объекты будут просто создаваться в соответствии с их классом хранения (в стеке или с помощью того, что вы определяете как новый оператор).

Это означает, что любой объект, который вы передаете своему конструктору полезной нагрузки, будет сначала создан (в стеке), а затем использован конструктором (который, скорее всего, скопирует его).

Полностью избежать этой копии невозможно. У вас может быть прямой конструктор, выполняющий минимальное количество копий, но тем не менее скалярные параметры, переданные инициализатору, скорее всего, будут скопированы, как и любые данные, которые конструктор инициализатора сочтет необходимым запомнить и/или создать.

Если вы хотите иметь возможность свободно передавать параметры каждому из конструкторов, необходимых для создания полного сообщения без их предварительного сохранения в объектах параметров, потребуется

  • использование оператора new размещения для каждого из подобъектов, составляющих сообщение,
  • запоминание каждого отдельного скалярного параметра, передаваемого различным подконструкторам,
  • специальный код для каждого объекта, чтобы передать оператору размещения новый правильный адрес и вызвать конструктор подобъекта.

В итоге вы получите конструктор сообщений верхнего уровня, принимающий все возможные начальные параметры и отправляющий их в различные конструкторы подобъектов.

Я даже не знаю, возможно ли это, но в любом случае результат будет очень хрупким и подверженным ошибкам.

Это то, что вы хотите, просто ради синтаксического сахара?

Если вы предлагаете API, вы не можете охватить все случаи. Лучший подход - сделать что-то, что хорошо деградирует, ИМХО.

Простым решением было бы ограничить параметры конструктора полезной нагрузки скалярными значениями или реализовать «подконструкцию на месте» для ограниченного набора полезных нагрузок сообщений, которыми вы можете управлять. На вашем уровне вы не можете сделать больше, чтобы убедиться, что построение сообщения продолжается без лишних копий.

Теперь прикладное программное обеспечение сможет свободно определять конструкторы, принимающие объекты в качестве параметров, и тогда расплатой будут эти дополнительные копии.

Кроме того, это может быть наиболее эффективным подходом, если параметр является чем-то дорогостоящим для создания (т. е. время создания больше, чем время копирования, поэтому более эффективно создавать статический объект и слегка изменять его между каждым сообщением) или если по какой-то причине он имеет большее время жизни, чем ваша функция.

рабочее, уродливое решение

Во-первых, давайте начнем со старинного решения без шаблонов, которое выполняет построение на месте.

Идея состоит в том, чтобы сообщение предварительно выделяло правильный тип памяти (локальный динамический буфер) в зависимости от размера объекта.
Затем надлежащий базовый адрес передается в новое место размещения для создания содержимого сообщения на месте. .

#include <cstdint>
#include <cstdio>
#include <new>

typedef uint8_t id_t;
enum class MessageID { WorldPeace, Armaggedon };

#define SMALL_BUF_SIZE 64

class Message {
    id_t     m_messageId;
    uint8_t* m_data;
    uint8_t  m_localData[SMALL_BUF_SIZE];

public:

    // choose the proper location for contents
    Message (MessageID messageId, size_t size)
    {
        m_messageId = (id_t)messageId;
        m_data = size <= SMALL_BUF_SIZE ? m_localData : new uint8_t[size];
    }

    // dispose of the contents if need be
    ~Message ()
    {
        if (m_data != m_localData) delete m_data;
    }

    // let placement new know about the contents location
    void * location (void)
    {
        return m_data;
    }
};

// a macro to do the in-place construction
#define BuildMessage(msg, id, obj, ...   )       \
        Message msg(MessageID::id, sizeof(obj)); \
        new (msg.location()) obj (__VA_ARGS__);  \

// example uses
struct small {
    int a, b, c;
    small (int a, int b, int c) :a(a),b(b),c(c) {}
};
struct big {
    int lump[1000];
};

int main(void)
{
    BuildMessage(msg1, WorldPeace, small, 1, 2, 3)
    BuildMessage(msg2, Armaggedon, big)
}

Это просто урезанная версия вашего исходного кода, вообще без шаблонов.

Я нахожу его относительно чистым и простым в использовании, но каждому свое.

Единственная неэффективность, которую я здесь вижу, это статическое выделение 64 байтов, которые будут бесполезны, если сообщение слишком большое.

И, конечно же, вся информация о типах теряется после создания сообщений, поэтому последующий доступ к их содержимому будет неудобным.

Об экспедировании и строительстве на месте

По сути, новый квалификатор && не творит чудес. Чтобы выполнить построение на месте, компилятору необходимо знать адрес, который будет использоваться для хранения объектов, до вызова конструктора.

После того, как вы вызвали создание объекта, память была выделена, и вещь && позволит вам использовать этот адрес только для передачи права собственности на указанную память другому объекту, не прибегая к бесполезным копиям.

Вы можете использовать шаблоны, чтобы распознать вызов конструктора Message с участием данного класса, переданного в качестве содержимого сообщения, но это будет слишком поздно: объект будет создан до того, как ваш конструктор сможет что-либо сделать с его расположением в памяти.

Я не вижу способа создать шаблон поверх класса Message, который бы откладывал построение объекта до тех пор, пока вы не решите, в каком месте вы хотите его построить.

Однако вы можете поработать над классами, определяющими содержимое объекта, чтобы автоматизировать построение на месте.

Это не решит общую проблему передачи объектов конструктору объекта, который будет построен на месте.

Для этого вам потребуется, чтобы сами подобъекты были созданы с помощью размещения new, что означало бы реализацию определенного интерфейса шаблона для каждого из инициализаторов, и чтобы каждый объект предоставлял адрес построения каждому из своих подобъектов. .

Теперь о синтаксическом сахаре.

Чтобы оправдать уродливые шаблоны, вы можете специализировать свои классы сообщений, чтобы они по-разному обрабатывали большие и маленькие сообщения.

Идея состоит в том, чтобы иметь один кусок памяти для передачи вашей функции отправки. Таким образом, в случае небольших сообщений заголовок и содержимое сообщения определяются как локальные свойства сообщения, а для больших сообщений выделяется дополнительная память для включения заголовка сообщения.

Таким образом, волшебный DMA, используемый для продвижения ваших сообщений через систему, будет иметь чистый блок данных для работы в любом случае.

Динамическое выделение по-прежнему будет происходить один раз для больших сообщений и никогда для маленьких.

#include <cstdint>
#include <new>

// ==========================================================================
// Common definitions
// ==========================================================================

// message header
enum class MessageID : uint8_t { WorldPeace, Armaggedon };
struct MessageHeader {
    MessageID id;
    uint8_t   __padding; // one free byte here
    uint16_t  size;
};

// small buffer size
#define SMALL_BUF_SIZE 64

// dummy send function
int some_DMA_trick(int destination, void * data, uint16_t size);

// ==========================================================================
// Macro solution
// ==========================================================================

// -----------------------------------------
// Message class
// -----------------------------------------
class mMessage {
    // local storage defined even for big messages
    MessageHeader   m_header;
    uint8_t         m_localData[SMALL_BUF_SIZE];

    // pointer to the actual message
    MessageHeader * m_head;
public:  
    // choose the proper location for contents
    mMessage (MessageID messageId, uint16_t size)
    {
        m_head = size <= SMALL_BUF_SIZE 
            ? &m_header
            : (MessageHeader *) new uint8_t[size + sizeof (m_header)];
        m_head->id   = messageId;
        m_head->size = size;
   }

    // dispose of the contents if need be
    ~mMessage ()
    {
        if (m_head != &m_header) delete m_head;
    }

    // let placement new know about the contents location
    void * location (void)
    {
        return m_head+1;
    }

    // send a message
    int send(int destination)
    {
        return some_DMA_trick (destination, m_head, (uint16_t)(m_head->size + sizeof (m_head)));
    }
};

// -----------------------------------------
// macro to do the in-place construction
// -----------------------------------------
#define BuildMessage(msg, obj, id, ...   )       \
        mMessage msg (MessageID::id, sizeof(obj)); \
        new (msg.location()) obj (__VA_ARGS__);  \

// ==========================================================================
// Template solution
// ==========================================================================
#include <utility>

// -----------------------------------------
// template to check storage capacity
// -----------------------------------------
template<typename T>
struct storage
{
    enum { local = sizeof(T)<=SMALL_BUF_SIZE };
};

// -----------------------------------------
// base message class
// -----------------------------------------
class tMessage {
protected:
    MessageHeader * m_head;
    tMessage(MessageHeader * head, MessageID id, uint16_t size) 
        : m_head(head)
    {
        m_head->id = id;
        m_head->size = size;
    }
public:
    int send(int destination)
    {
        return some_DMA_trick (destination, m_head, (uint16_t)(m_head->size + sizeof (*m_head)));
    }
};

// -----------------------------------------
// general message template
// -----------------------------------------
template<bool local_storage, typename message_contents>
class aMessage {};

// -----------------------------------------
// specialization for big messages
// -----------------------------------------
template<typename T>
class aMessage<false, T> : public tMessage
{
public:
    // in-place constructor
    template<class... Args>
    aMessage(MessageID id, Args...args) 
        : tMessage(
            (MessageHeader *)new uint8_t[sizeof(T)+sizeof(*m_head)], // dynamic allocation
            id, sizeof(T))
    {
        new (m_head+1) T(std::forward<Args>(args)...);
    }

    // destructor
    ~aMessage ()
    {
        delete m_head;
    }

    // syntactic sugar to access contents
    T& contents(void) { return *(T*)(m_head+1); }
};

// -----------------------------------------
// specialization for small messages
// -----------------------------------------
template<typename T>
class aMessage<true, T> : public tMessage
{
    // message body defined locally
    MessageHeader m_header;
    uint8_t       m_data[sizeof(T)]; // no need for 64 bytes here

public:
    // in-place constructor
    template<class... Args>
    aMessage(MessageID id, Args...args) 
        : tMessage(
            &m_header, // local storage
            id, sizeof(T))
    {
        new (m_head+1) T(std::forward<Args>(args)...);
    }

    // syntactic sugar to access contents
    T& contents(void) { return *(T*)(m_head+1); }
};


// -----------------------------------------
// helper macro to hide template ugliness
// -----------------------------------------
#define Message(T) aMessage<storage<T>::local, T>
// something like typedef aMessage<storage<T>::local, T> Message<T>

// ==========================================================================
// Example
// ==========================================================================
#include <cstdio>
#include <cstring>

// message sending
int some_DMA_trick(int destination, void * data, uint16_t size)
{
    printf("sending %d bytes @%p to %08X\n", size, data, destination);
    return 1;
}

// some dynamic contents
struct gizmo {
    char * s;
    gizmo(void) { s = nullptr; };
    gizmo (const gizmo&  g) = delete;

    gizmo (const char * msg)
    {
        s = new char[strlen(msg) + 3];
        strcpy(s, msg);
        strcat(s, "#");
    }

    gizmo (gizmo&& g)
    {
        s = g.s;
        g.s = nullptr;
        strcat(s, "*");
    }

    ~gizmo() 
    { 
        delete s;
    }

    gizmo& operator=(gizmo g)
    {
        std::swap(s, g.s);
        return *this;
    }
    bool operator!=(gizmo& g)
    {
        return strcmp (s, g.s) != 0;
    }

};

// some small contents
struct small {
    int a, b, c;
    gizmo g;
    small (gizmo g, int a, int b, int c)
        : a(a), b(b), c(c), g(std::move(g)) 
    {
    }

    void trace(void) 
    { 
        printf("small: %d %d %d %s\n", a, b, c, g.s);
    }
};

// some big contents
struct big {
    gizmo lump[1000];

    big(const char * msg = "?")
    { 
        for (size_t i = 0; i != sizeof(lump) / sizeof(lump[0]); i++)
            lump[i] = gizmo (msg);
    }

    void trace(void)
    {
        printf("big: set to ");
        gizmo& first = lump[0];
        for (size_t i = 1; i != sizeof(lump) / sizeof(lump[0]); i++)
            if (lump[i] != first) { printf(" Erm... mostly "); break; }
        printf("%s\n", first.s);
    }
};

int main(void)
{
    // macros
    BuildMessage(mmsg1, small, WorldPeace, gizmo("Hi"), 1, 2, 3);
    BuildMessage(mmsg2, big  , Armaggedon, "Doom");
    ((small *)mmsg1.location())->trace();
    ((big   *)mmsg2.location())->trace();
    mmsg1.send(0x1000);
    mmsg2.send(0x2000);

    // templates
    Message (small) tmsg1(MessageID::WorldPeace, gizmo("Hello"), 4, 5, 6);
    Message (big  ) tmsg2(MessageID::Armaggedon, "Damnation");
    tmsg1.contents().trace();
    tmsg2.contents().trace();
    tmsg1.send(0x3000);
    tmsg2.send(0x4000);
}

выход:

small: 1 2 3 Hi#*
big: set to Doom#
sending 20 bytes @0xbf81be20 to 00001000
sending 4004 bytes @0x9e58018 to 00002000
small: 4 5 6 Hello#**
big: set to Damnation#
sending 20 bytes @0xbf81be0c to 00003000
sending 4004 bytes @0x9e5ce50 to 00004000

Пересылка аргументов

Я не вижу большого смысла в пересылке параметров конструктора здесь.

Любой бит динамических данных, на который ссылается содержимое сообщения, должен быть либо статическим, либо скопирован в тело сообщения, иначе данные, на которые ссылаются, исчезнут, как только создатель сообщения выйдет из области действия.

Если пользователи этой удивительно эффективной библиотеки начнут передавать магические указатели и другие глобальные данные внутри сообщений, мне интересно, как это повлияет на глобальную производительность системы. Но это не мое дело, в конце концов.

Макросы

Я прибегнул к макросу, чтобы скрыть уродство шаблона в определении типа.

Если у кого-то есть идея избавиться от него, мне интересно.

Эффективность

Вариант шаблона требует дополнительной пересылки параметров содержимого для достижения конструктора. Я не понимаю, как этого можно избежать.

Версия макроса тратит 68 байт памяти на большие сообщения и часть памяти на маленькие (64 - sizeof (contents object)).

С точки зрения производительности, этот дополнительный бит памяти — единственное преимущество, предлагаемое шаблонами. Поскольку все эти объекты предположительно создаются в стеке и живут несколько микросекунд, ими можно пренебречь.

По сравнению с вашей первоначальной версией, эта версия должна более эффективно обрабатывать большие сообщения. Опять же, если эти сообщения редки и предлагаются только для удобства, разница не очень полезна.

Версия шаблона поддерживает один указатель на полезную нагрузку сообщения, который можно было бы сэкономить для небольших сообщений, если бы вы реализовали специализированную версию функции send.
Едва ли стоит дублировать код, ИМХО.

Последнее слово

Я думаю, что довольно хорошо знаю, как работает операционная система и какие могут быть проблемы с производительностью. В свое время я написал довольно много приложений реального времени, а также несколько драйверов и пару BSP.

Я также не раз видел очень эффективный системный уровень, разрушенный слишком либеральным интерфейсом, который позволял программистам прикладного программного обеспечения делать самые глупые вещи, даже не подозревая об этом.
Это то, что вызвало мою первоначальную реакцию.

Если бы я имел право голоса в глобальном системном проектировании, я бы запретил все эти магические указатели и прочее скрытое смешение со ссылками на объекты, чтобы ограничить неспециалистов пользователей безобидным использованием системных слоев, вместо того, чтобы позволить им непреднамеренно распространять тараканов по системе.

Если пользователи этого интерфейса не разбираются в шаблонах и реальном времени, они немного не поймут, что происходит под синтаксической сахарной корочкой, и очень скоро могут выстрелить себе (и своим коллегам и прикладному программному обеспечению) в ногу. .

Предположим, что плохой программист прикладного программного обеспечения добавляет крошечное поле в одну из своих структур и неосознанно пересекает барьер в 64 байта. Внезапно производительность системы рухнет, и вам понадобится мистер шаблон и эксперт в реальном времени, чтобы объяснить бедняге, что то, что он сделал, убило много котят.
Что еще хуже, деградация системы может быть прогрессирующей или незаметной в во-первых, так что в один прекрасный день вы можете проснуться с тысячами строк кода, которые выполняли динамическое распределение в течение многих лет, и никто этого не замечал, и глобальная переработка для исправления проблемы может быть огромной.

Если, с другой стороны, все люди в вашей компании жуют шаблоны и мьютексы на завтрак, синтаксический сахар вообще не требуется.

person kuroi neko    schedule 01.02.2014
comment
Так что да. Не совсем уверен, с чего начать. Так что я просто выберу самые очевидные и вопиющие. if (m_data == m_localData) m_data = new uint8_t[new_size]; ... else m_data = (uint8_t*) realloc (m_data, new_size);. Давайте сократим это: if X new ELSE realloc и троичный вариант, который я написал, позволяет GCC, ICC, Clang и MSVC использовать условное присваивание, а не ветвь. И как вы рассматриваете выделение объекта с помощью new и запоминание его в сообщении не копия? Плюс второстепенный момент, что ваш код не предусматривает построение на месте. - person kfsone; 05.02.2014
comment
В моем sscce я случайно оставил grow общедоступным - я не видел смысла специально идти и помечать его private для sscce. Что касается локального буфера по умолчанию, фактический код объединяет эти объекты, и большой процент сообщений, особенно в ситуациях высокой нагрузки, обычно достаточно мал, чтобы поместиться в эти буферы. Главной причиной сложности этой системы является большое количество нижестоящих пользователей. Намерение здесь состоит в том, чтобы работать с компилятором в достаточной степени, чтобы непосредственным пользователям API не приходилось изучать эту мерзость оптимизации. - person kfsone; 05.02.2014
comment
Метод Grow() никогда не вызывается в конструкторе. Я включил его только для того, чтобы соответствовать тому, что я понял из исходного запроса. Объекты размером менее 64 байт действительно копируются (что занимает ничтожно малое время), а большие — нет. Если вы хотите впоследствии увеличить сообщение (что, ИМХО, делать безбожно), поскольку вся информация о вводе в любом случае теряется, у вас нет другого выбора, кроме как прибегнуть к копии. - person kuroi neko; 05.02.2014
comment
Наконец, система, над которой я работаю, представляет собой рефакторинг моего собственного кода. Система невероятно стабильна, невероятно эффективна библиотека передачи сетевых сообщений. Он работает уже 10 лет, а текущая версия почти 6 лет. Сложность внутренней части в значительной степени не позволила разработчикам, работающим с ним, совершать глупые ошибки, которые убивают клиентов или серверы из-за сети, за исключением нескольких случаев, и я добился успеха в использовании методов С++ 11, чтобы помочь компилятору обработать их за них. - person kfsone; 05.02.2014
comment
В вашем варианте требуется, чтобы вызывающий объект выделил объект и передал указатель на него, а затем memcpy() вставил содержимое в объект. Мой вариант упорядочивает данные непосредственно в объект. Если бы мы конструировали объект X байт с вашим, вам пришлось бы malloc X байтов кучи, заполнить их, затем передать адрес в Message::Message(), который затем либо malloc() второй X байтов и передать объект в это, иначе он скопирует его в кучу. - person kfsone; 05.02.2014
comment
В существующей системе или варианте C++11 построение выполняется один раз. Используя шаблоны, конструктор Message::Message() фактически предоставляет единственное место назначения для объекта, в котором будет создан объект. Объект не создается вне конструктора Message::Message. - person kfsone; 05.02.2014
comment
Похоже, вы воспринимаете мои предложения как личные нападки. Я просто отметил, что вся система показалась мне чрезмерно сложной, особенно когда она сводится к предотвращению копирования размером менее 64 байт. Мой совет заключается в том, что конструкция на месте не нужна, и, кроме того, я не думаю, что С++ 11 ее поддерживает, как четко указано в другом ответе. - person kuroi neko; 05.02.2014
comment
Не воспринято как нападки, просто пытаюсь понять, имеете ли вы просто очень ограниченное понимание того, что вы обсуждаете, или вы что-то упускаете из виду в моем вопросе. Когда я говорю, что многие сообщения имеют размер менее 64 байт, это потому, что мы имеем дело с десятками тысяч сообщений в секунду. - person kfsone; 05.02.2014
comment
Что делает мой код: содержимое сообщения создается вызывающим абонентом. Конструктор сообщения берет на себя ответственность за содержимое. Если он меньше 64 байт, он копируется и удаляется. Если он больше, сообщение сохраняет ссылку на него без какой-либо копии. Объект уничтожается, когда сообщение. Стоимость небольших сообщений составляет новое/удаление плюс копия размером менее 64 байт. Если бы мне пришлось это сделать, я бы отказался от всего этого маленького буфера и заменил его настраиваемым распределителем, рисующим из пула предварительно выделенных 64-байтовых буферов, чтобы системе сообщений не пришлось со всем этим возиться. - person kuroi neko; 05.02.2014
comment
особенно когда это сводится к предотвращению копирования менее 64 байтов, это сводится к предотвращению копий и ненужному загрязнению кеша или вызову передачи памяти, это, безусловно, включает в себя отсутствие необходимости выполнять выделение памяти при каждом вызове. система - минимальный буфер сообщений в системах, подобных этим (см. raknet для параллельного сравнения), во многих случаях позволяет вам получить все ваше сообщение за одно выделение. Вам придется поверить мне на слово, это необходимо. - person kfsone; 05.02.2014
comment
давайте продолжим это обсуждение в чате - person kuroi neko; 05.02.2014
comment
Он не становится владельцем — он выделяет память и побитовые копии в новое место. Это не работает для нетривиальных объектов C++. И понимаете ли вы, что для создания, перераспределения и удаления требуется вызов мьютекса? Для подавляющего большинства сообщений мой класс полностью находится в стеке, и, перенаправляя создание подобъекта в мой конструктор, я могу сделать это все в стеке. - person kfsone; 05.02.2014
comment
Мой вопрос конкретно касается использования инструментов C++11 для достижения результата, а не попытки решить конкретную проблему. Хотя я ценю ваши усилия по попытке помочь с проблемами, которые вы видите в этом весьма производном отображении очень высокоэффективной системы передачи сообщений, эти решения и не нужны, и не очень хороши - например, то, что вы Достигнутый в основном макрос до C++11, который я включил в свой исходный пост, CONSTRUCT_IN_PLACE, а в вашем BuildMessage...Gizmo("Hi") вы создаете временный Gizmo, который BuildMessage затем должен скопировать в само сообщение. - person kfsone; 06.02.2014
comment
Выявленные вами проблемы (потеря памяти и т. д.) в основном являются артефактами того, что я поднял только часть кода и не предоставил подробного понимания того, что делает система. Ваш комментарий о шаблоне, создающем дополнительную переадресацию, абсолютно неверен, если только вы не имеете в виду самую последнюю версию в моем OP, которую я задаю вопрос о том, можно ли это сделать с трюками C++ 11 . Предыдущие версии избегают временных файлов и копий и создают новости на месте для сериализованных объектов. Вам придется поверить мне на слово или проконсультироваться с собранием. - person kfsone; 06.02.2014
comment
Я говорю о своем собственном коде. В моем примере конструкторы полезной нагрузки сообщения вызываются внутри конструктора сообщения, поэтому параметры должны передаваться дважды. Я не понимаю, как вы могли бы избежать этого, не вызывая конструктор полезной нагрузки напрямую (как в решении для макросов). Что касается штуковины, то она просто материализовала двойную передачу аргументов. Очевидно, что в реальном случае это не сработает. - person kuroi neko; 06.02.2014
comment
Во всяком случае, этот ваш вопрос предоставил мне практический пример шаблонов C++ 11, который мне показался интересным. Мое решение сводится к варианту другого ответа, то есть получить класс, который знает о размере и аргументах конструктора полезной нагрузки сообщения, чтобы отложить вызов конструктора до тех пор, пока не будет выбрано правильное место в памяти. Что касается динамических выделений, кроме явного в случае больших сообщений, то я не вижу, где они встречаются в моем примере. Извините, если это не соответствует вашим потребностям. - person kuroi neko; 06.02.2014