2013-01-10 87 views
1

我有一個函數,我希望編譯器始終使用NRVO ......即使在調試模式下。這是否有一個附註?如何在msvc中強制返回值優化

這裏是我的類,它工作在「釋放」模式極大:

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不起作用:

#pragma optimize("gf", on) 

任何想法?

+0

[對您有所幫助嗎?](http://stackoverflow.com/questions/13618506/is-it-possible-to-stdmove-objects-out-of-functions-c11/13618587#13618587) – billz

+2

什麼?問題是你想解決這個問題的解決方案嗎? – GManNickG

+1

爲什麼?代碼的語義不會改變。 –

回答

1

我不認爲有一個公開的細粒度編譯器選項,只觸發NRVO。

但是,您仍然可以通過更改項目設置,命令行和#pragma中的選項來對每個源文件操作編譯器優化標誌。

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

儘量給/ O1或/ O2到你想要的文件。

而且,Visual C++中的調試模式只不過是沒有優化並生成調試信息(PDB,程序數據庫文件)的配置。

+0

+1確認沒有編譯器選項。 – johnnycrash

+0

順便說一句,我發佈這個之前,我嘗試了所有這些#pragmas,我找不到任何工作。 – johnnycrash

1

如果您使用的是Visual C++ 2010或更高版本,則可以使用移動語義來獲得等效的結果。見How to: Write a Move Constructor

+0

就我而言,移動並不是很棒,因爲我在課堂上有一個很大的就地緩衝區。 – johnnycrash

+0

+1用於提示移動語義。重要的東西。然而,移動語義與NRVO和RVO的不同之處在於,在我的情況中,並不是那麼好。 – johnnycrash

+0

@johnnycrash在這種情況下,如果用例是優化多餘副本,則需要在啓用優化的情況下進行編譯。 – Neil

2

讓代碼既符合標準又符合標準並不困難。

首先,用可選的額外填充來包裝T的數組。現在你知道佈局。

對於所有權使用獨特的ptr而不是原始的ptr。如果它是虛擬的,則運算符T *返回它,否則返回緩衝區。現在您的默認移動工作,如果移動失敗,就像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是POD)。

在我自己投入生產之前,我會添加一些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'd情況:

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_ptr是可移動構造的,就像struct中的數組一樣。還請注意,複製構造被阻止,因爲unique_ptr不能被複制構建:這符合您的需求。

在調試中,很可能你最終會做一個移動構造。但是這不應該有任何傷害。

+0

我不確定我完全理解你的意思。大部分時間我都試圖實現一個基於堆棧的緩衝區,所以我怎樣才能避免使用m_pBuf [_cbStack]?如果我無法解決這個問題,那麼當移動或複製被調用時,我必須複製正在使用的內部緩衝區的一部分。 – johnnycrash

+0

+1可能的解決方案。 – johnnycrash

+0

@johnnycrash首先,默認移動副本應該在結構中的數組中的POD類型上工作。所以,如果你有一個數組和一個'unique_ptr'到可能的非數組內存中,那麼對於POD類型它將會「正常工作」。如果它不存在,則返回ptr到unique_ptr內存,否則返回到緩衝區開始的ptr。在'unique_ptr'上移動ctor就像在一個struct中的數組上移動一樣。鮑勃是你的親戚,也許是你母親的兄弟。 – Yakk