У меня есть функция в классе, которую я хочу, чтобы компилятор использовал NRVO … все время … даже в режиме отладки. Есть ли для этого прагма?
Вот мой класс, который отлично работает в режиме «релиз»:
template <int _cbStack> class CBuffer {
public:
CBuffer(int cb) : m_p(0) {
m_p = (cb > _cbStack) ? (char*)malloc(cb) : m_pBuf;
}
template <typename T> operator T () const {
return static_cast<T>(m_p);
}
~CBuffer() {
if (m_p && m_p != m_pBuf)
free(m_p);
}
private:
char *m_p, m_pBuf[_cbStack];
};
Класс используется для создания буфера в стеке, если не требуется больше, чем _cbStack байтов. Затем, когда он разрушается, он освобождает память, если он выделен. Это удобно при взаимодействии с функциями c, которым требуется строковый буфер, и вы не уверены в максимальном размере.
В любом случае, я пытался написать функцию, которая могла бы возвращать CBuffer, как в этом тесте:
#include "stdafx.h"#include <malloc.h>
#include <string.h>
template <int _cbStack> CBuffer<_cbStack> foo()
{
// return a Buf populated with something...
unsigned long cch = 500;
CBuffer<_cbStack> Buf(cch + 1);
memset(Buf, 'a', cch);
((char*)Buf)[cch] = 0;
return Buf;
}
int _tmain(int argc, _TCHAR* argv[])
{
auto Buf = foo<256>();
return 0;
}
Я рассчитывал, что NRVO сделает foo () быстрым. В режиме релиза он отлично работает. В режиме отладки это, очевидно, дает сбой, потому что в моем классе нет конструктора копирования. Я не хочу конструктор копирования, так как CBuffer будет использоваться разработчиками, которые любят копировать все 50 раз. (Rant: эти парни использовали класс динамического массива для создания буфера из 20 символов для передачи в WideCharToMultiByte (), потому что они, кажется, забыли, что вы можете просто разместить массив символов в стеке. Я не знаю, они даже знают что такое стек …)
Я действительно не хочу кодировать конструктор копирования просто так, чтобы код работал в режиме отладки! Это становится огромным и сложным:
template <int _cbStack>
class CBuffer {
public:
CBuffer(int cb) : m_p(0) { Allocate(cb); }
CBuffer(CBuffer<_cbStack> &r) {
int cb = (r.m_p == r.m_pBuf) ? _cbStack : ((int*)r.m_p)[-1];
Allocate(cb);
memcpy(m_p, r.m_p, cb);
}
CBuffer(CBuffer<_cbStack> &&r) {
if (r.m_p == r.m_pBuf) {
m_p = m_pBuf;
memcpy(m_p, r.m_p, _cbStack);
} else {
m_p = r.m_p;
r.m_p = NULL;
}
}
template <typename T> operator T () const {
return static_cast<T>(m_p);
}
~CBuffer() {
if (m_p && m_p != m_pBuf)
free((int*)m_p - 1);
}
protected:
void Allocate(int cb) {
if (cb > _cbStack) {
m_p = (char*)malloc(cb + sizeof(int));
*(int*)m_p = cb;
m_p += sizeof(int);
} else {
m_p = m_pBuf;
}
}
char *m_p, m_pBuf[_cbStack];
};
Эта прагма не работает
#pragma optimize("gf", on)
Есть идеи?
Нетрудно сделать так, чтобы ваш код соответствовал стандартам и работал.
Во-первых, оберните массивы T дополнительными дополнительными отступами. Теперь вы знаете макет.
Для владения используйте уникальный ptr вместо необработанного. Если это vapid, оператор T * возвращает его, в противном случае, буфер. Теперь ваш ctor хода по умолчанию работает, как и NRVO, если перемещение не удалось.
Если вы хотите поддерживать не POD-типы, небольшая работа позволит вам поддерживать и ctors, и dtors, а также перемещать элементы массива и бит заполнения для бита.
Результатом будет класс, который не будет вести себя удивительно и не будет создавать ошибки, когда кто-то попытается скопировать или переместить его в первый раз — ну, не первый, это будет легко. Код, как написано, будет взорваться по-разному в разное время!
Соблюдайте правило трех.
Вот явный пример (теперь, когда я отключен от телефона):
template <size_t T, size_t bufSize=sizeof(T)>
struct CBuffer {
typedef T value_type;
CBuffer();
explicit CBuffer(size_t count=1, size_t extra=0) {
reset(count, extra);
}
void resize(size_t count, size_t extra=0) {
size_t amount = sizeof(value_type)*count + extra;
if (amount > bufSize) {
m_heapBuffer.reset( new char[amount] );
} else {
m_heapBuffer.reset();
}
}
explicit operator value_type const* () const {
return get();
}
explicit operator value_type* () {
return get();
}
T* get() {
return reinterpret_cast<value_type*>(getPtr())
}
T const* get() const {
return reinterpret_cast<value_type const*>(getPtr())
}
private:
std::unique_ptr< char[] > m_heapBuffer;
char m_Buffer[bufSize];
char const* getPtr() const {
if (m_heapBuffer)
return m_heapBuffer.get();
return &m_Buffer[0];
}
char* getPtr() {
if (m_heapBuffer)
return m_heapBuffer.get();
return &m_Buffer[0];
}
};
Выше CBuffer
поддерживает конструкцию перемещения и назначение перемещения, но не копирует конструкцию или назначение копирования. Это означает, что вы можете вернуть их локальный экземпляр из функции. RVO может произойти, но если этого не произойдет, приведенный выше код все еще безопасен и легален (при условии T
это ПОД).
Прежде чем запустить его в производство, я бы добавил T
должно быть POD утверждает выше, или обрабатывать не POD T
,
В качестве примера использования:
#include <iostream>
size_t fill_buff(size_t len, char* buff) {
char const* src = "This is a string";
size_t needed = strlen(src)+1;
if (len < needed)
return needed;
strcpy( buff, src );
return needed;
}
void test1() {
size_t amt = fill_buff(0,0);
CBuffer<char, 100> strBuf(amt);
fill_buff( amt, strBuf.get() );
std::cout << strBuf.get() << "\n";
}
И для (надеюсь) случая NRVO:
template<size_t n>
CBuffer<char, n> test2() {
CBuffer<char, n> strBuf;
size_t amt = fill_buff(0,0);
strBuf.resize(amt);
fill_buff( amt, strBuf.get() );
return strBuf;
}
который, если происходит NRVO (как и должно быть), не нуждается в перемещении, а если NRVO не происходит, то неявное перемещение, которое происходит, логически эквивалентно не выполнению перемещения.
Дело в том, что NRVO не полагается на четко определенное поведение. Однако NRVO почти наверняка произойдет, и когда это произойдет, он сделает что-то логически эквивалентное выполнению опции move-constructor.
Мне не нужно было писать такой Move-конструктор, потому что unique_ptr
является конструируемым в движении, как и массивы внутри struct
s. Также обратите внимание, что копирование-конструкция заблокирована, потому что unique_ptr
не может быть создан с помощью копирования: это соответствует вашим потребностям.
В отладке вполне возможно, что в конечном итоге вы создадите движение-конструкцию. Но в этом не должно быть никакого вреда.
Я не думаю, что есть общедоступная опция детального компилятора, которая только запускает NRVO.
Однако вы все равно можете управлять флагами оптимизации компилятора для каждого исходного файла, изменяя параметры в настройках проекта, командной строке и #pragma
,
http://msdn.microsoft.com/en-us/library/chh3fb0k(v=vs.110).aspx
Попробуйте указать / O1 или / O2 для файла, который вы хотите.
И режим отладки в Visual C ++ — это не что иное, как конфигурация без оптимизации и генерации отладочной информации (PDB, файл базы данных программы).
Если вы используете Visual C ++ 2010 или более позднюю версию, вы можете использовать семантику перемещения для достижения эквивалентного результата. Увидеть Как: написать конструктор Move.