![](/img/trans.png)
[英]Is there any reason not to use std::make_shared when constructing objects?
[英]std::make_shared fails to compile when constructing with parameters from Bitfields
請考慮以下最小的可重新創建的標准兼容代碼
#include <vector>
#include <memory>
struct Foo
{
int m_field1;
Foo(int field1):m_field1(field1){};
};
typedef unsigned long DWORD;
typedef unsigned short WORD;
struct BitField {
struct {
DWORD Field1:31;
DWORD Field2:1;
} DUMMY;
};
int main()
{
std::vector<std::shared_ptr<Foo>> bar;
BitField *p = new BitField();
//This Line compiles
auto sp1 = std::shared_ptr<Foo>(new Foo((DWORD)p->DUMMY.Field1));
//But std::make_shared fails to compile
auto sp2 = std::make_shared<Foo>((DWORD)p->DUMMY.Field1);
return 0;
}
此代碼無法在VC11 Update 2中編譯,並顯示以下錯誤消息
1>Source.cpp(23): error C2664: 'std::shared_ptr<_Ty> std::make_shared<Foo,DWORD&>(_V0_t)' : cannot convert parameter 1 from 'DWORD' to 'unsigned long &'
1> with
1> [
1> _Ty=Foo,
1> _V0_t=DWORD &
1> ]
我在IDEONE上交叉檢查,並且編譯成功。 我錯過了一些明顯的東西嗎
這是一個奇怪的。 下面的代碼片段在/Za
(禁用語言擴展)編譯器標志下編譯,但不是沒有:
struct {
unsigned field:1;
} dummy = {0};
template<class T>
void foo(T&&){}
int main(){
foo((unsigned)dummy.field);
}
沒有/Za
出錯:
錯誤C2664:'foo':無法將參數1從'unsigned int'轉換為'unsigned int&'
這顯然是一個錯誤,因為轉換為unsigned
應該只創建一個rvalue,它不應該推導為左值引用,不應該被視為位字段。 我有一種感覺“rvalues綁定到左值參考”的擴展在這里起作用。
請提交有關Microsoft Connect的錯誤報告。
編譯器的錯誤消息是正確的,因為它實際上無法根據您傳入的值創建DWORD&
。位域不是正確的大小,是對DWORD
的真正引用。 編譯器是否正確拒絕您的程序,我不能說。
但是,這很容易解決。 調用make_shared
時,只需指定第二個模板參數:
auto sp2 = std::make_shared<Foo, int>(p->DUMMY.Field1);
我使用int
因為這是構造函數的參數類型。 你可以說DWORD
; 任何非引用數字類型都可能就足夠了。 然后,您也可以放棄將類型轉換為DWORD
。 它沒有做更多的事情。
這里有更多的評論而不是答案。 它可能會對正在發生的事情有所了解。
Xeo的例子
struct {
unsigned field:1;
unsigned nonfield;
} dummy = {0};
template<class T>
void foo(T&&){}
第一步:輸入扣除。
[class.bit] / 1指定“位字段屬性不屬於類成員的類型”。 因此, foo(dummy.field)
推導推導出模板參數為unsigned&
。
第二步:重載解析。
雖然這里沒有嚴格要求,但標准在[over.ics.ref] / 4中有一個很好的例子
[示例:即使相應的參數是
int
位域,具有“左值引用到int
”參數的函數也可以是可行的候選者。 隱式轉換的序列的形成對待int
位字段作為int
左值和找到與參數的精確匹配。 如果通過重載決策選擇該函數,則由於禁止將非常量左值引用綁定到位字段(8.5.3),因此調用將是格式錯誤的。 - 末端的例子]
因此,這個功能是完善的,將被選中,但呼叫將是不正確的。
第三步:解決方法。
OP的轉換應解決問題, foo( (unsigned)dummy.field )
,因為它產生一個rvalue,導致T
被推導為unsigned
,並且參數unsigned&&
從臨時初始化。 但是,如果源和目標具有相同的類型,MSVC似乎忽略了左值到右值的轉換。 寫foo( (unsigned)dummy.nonfield )
推導T
作為T&
以及(即使有static_cast
)。
將T
推導為unsigned
而非unsigned&
所需的左值到右值轉換可以通過使用一元+
: foo( +dummy.field )
來強制執行
位字段不能有引用,但有時會像左值一樣處理,因為它們可以分配給它們。 比特字段是混亂的IMO,你應該避免它們。
但如果您需要將位域轉換為與同一類型的右值完全相同的行為,則可以使用如下所示的函數。
template<class T>
T frombits(const T& x) {
return x;
}
//...
std::make_shared<Foo>(frombits(p->DUMMY.Field1));
我寧願反對指定模板類型。 如果可以,並且總是在預期的時候,讓編譯器推斷出類型。 模板參數推導在C ++ 11中可能會變得混亂,但它已被設計為非常好用,幾乎在所有情況下都能正常工作。 不要幫助編譯器,不要認為你比它更清楚; 最終你會松動。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.