1

クラスに関数があり、デバッグモードでもコンパイラに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;
}

私はfoo()を高速にするためにNRVOを頼りにしていました。リリースモードでは、うまく機能します。デバッグモードでは、クラスにコピーコンストラクターがないため、明らかに失敗します。CBufferは、すべてを50回コピーしたい開発者によって使用されるため、コピーコンストラクターは必要ありません。(Rant:これらの人は動的配列クラスを使用してWideCharToMultiByte()に渡す20文字のバッファーを作成していました。スタックに文字の配列を割り当てることができることを忘れているようです。彼らはスタックが何であるかさえ知っています...)

コードがデバッグモードで動作するように、コピーコンストラクターをコーディングしたくありません。それは巨大で複雑になります:

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)

何か案は?

4

3 に答える 3

2

コードを標準に準拠させて機能させることは難しくありません。

最初に、オプションの余分なパディングを使用して T の配列をラップします。これでレイアウトがわかりました。

所有権については、生のptrではなく一意のptrを使用してください。vapid の場合、オペレーター T* はそれを返し、それ以外の場合はバッファーします。これで、移動が失敗した場合の NRVO と同様に、デフォルトの移動 ctor が機能します。

POD 以外の型をサポートしたい場合は、ちょっとした作業で ctor と dtor の両方をサポートし、配列要素を移動してビットごとにパディングすることができます。

その結果、誰かが最初にそれをコピーまたは移動しようとしたときに、驚くような動作をせず、バグを作成しないクラスになります。書かれたコードは、さまざまな時期にさまざまな方法で爆破されます!

3のルールに従います。

これが明確な例です(今、私は自分の電話から離れています):

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です (POD と仮定)。

自分で本番環境に入れる前に、一部のTmust be 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 オプションを実行するのと論理的に同等の処理を行います。

unique_ptrs内の配列と同様に、移動構築可能であるため、そのような移動コンストラクタを記述する必要はありませんでしたstruct。また、コピー構築できないため、コピー構築がブロックされていることに注意しunique_ptrてください。これは、ニーズに合わせて調整されます。

デバッグでは、move-construct を実行することになる可能性が非常に高いです。しかし、それで害があるはずはありません。

于 2013-01-11T00:38:21.500 に答える
1

Visual C ++ 2010以降を使用している場合は、移動セマンティクスを使用して同等の結果を得ることができます。方法:移動コンストラクターを作成するを参照してください。

于 2013-01-11T00:09:45.820 に答える
1

NRVO のみをトリガーする、公開されているきめの細かいコンパイラ オプションはないと思います。

ただし、プロジェクト設定、コマンド ライン、および#pragma.

http://msdn.microsoft.com/en-us/library/chh3fb0k(v=vs.110).aspx

必要なファイルに /O1 または /O2 を付与してみてください。

また、Visual C++ のデバッグ モードは、最適化を行わず、デバッグ情報 (PDB、プログラム データベース ファイル) を生成する構成に他なりません。

于 2013-01-10T23:59:05.927 に答える