我一直試圖了解std::bind是如何作業的。所以用不同的例子來作業。下面是我無法理解的輸出的示例程式。
VERSION 1
class NAME
{
public:
void f().
{
std::cout<<"f"<<std::endl。
}
NAME()
{
std::cout<<"默認建構式"<<std::endl。
}
NAME(const NAME& )
{
std::cout<<"復制建構式"<<std::endl。
}
};
int main()
{
std::cout << "Hello World" << std::endl。
名稱n。
std::function<void ()> callable = std::bind(&NAME::f, n)。
return 0;
}
上述版本1的輸出如下:
Hello World
default構造器
復制建構式
復制建構式
我知道傳遞的引數將被復制,所以復制建構式應該只被呼叫一次,但在上面的輸出中,復制建構式被呼叫了兩次。為什么/怎么會發生這種情況?是不是因為使用 std::bind 創建的新可呼叫程式將被用于初始化另一個使用 std::function 的 lhs 的可呼叫程式?
VERSION 2
int main()
{
std::cout << "Hello World" << std::endl;
名稱n。
std::function<void ()> callable = std::move(std::bind(& NAME::f, n))。
return 0。
}
VERSION 2的輸出如下:
Hello World
default構造器
復制建構式
拷貝建構式
拷貝建構式
在上面的輸出中(版本2),當我使用std::move時,為什么/如何復制建構式被呼叫三次?
版本3
int main()
{
std::cout << "Hello World" << std::endl。
名稱n。
auto callable = std::bind(&NAME::f, n)。
return 0;
}
版本3的輸出如下:
Hello World
default構造器
復制建構式
在這種情況下(第3版),為什么/如何復制建構式只被呼叫一次?
版本4
int main()
{
std::cout << "Hello World" << std::endl;
名稱n。
auto callable = std::move(std::bind(&NAME::f, n)) 。
return 0。
}
版本4的輸出如下:
Hello World
default構造器
復制建構式
復制建構式
當我們使用auto和std::move()時,在這種情況下(版本4)會發生什么,為什么/如何復制構造器被呼叫兩次。
PS:該程式是在一個在線編譯器上執行的。使用的在線編譯器
編輯:閱讀評論后,我有一個進一步的問題/疑惑:
問題1
如果我使用auto callable_1 = std::bind(&NAME::f, n);和std::function<void ()> callable_2 = std::bind(&NAME::f, n); 那么callable_1和callable_2的型別是否不同?如果是,那么auto為callable_1推導的型別是什么?還是說auto為callable_1推匯出的型別將是一個未命名的類物件,就像對lambda一樣。
問題2
我有一個進一步的問題。正如我所做的,如果我在std::bind中傳遞n而不是&n,那么這個代碼是否合法?例如,如果我寫std::function<void ()> callable_1 = std::bind(&NAME::f, n);和auto callable_2 = std::bind(&NAME::f, n);那么這兩段代碼是否違法?如果我在這兩種情況下通過ref(n)而不是n,那么它們是非法還是合法呢?
問題3
auto callable = [&n]{n.f()};和auto callable = std::bind(&NAME::f, cref(n));(在功能上)和其他一些方面是否相等,除了編碼風格之外,是否有任何理由(優勢)選擇一個而不是另一個?
問題4
如果我寫auto callable = std::bind(&NAME::f, 1_); callable(n); callable(std::ref(n)); 。陳述句callable(n)是否非法?那么陳述句callable(std::ref(n));呢?
uj5u.com熱心網友回復:
首先,根據移動建構式的規則,沒有為類NAME定義隱式移動建構式。此外,從注釋這里:
如果只提供復制建構式,所有的引數類別 都會選擇它(只要它需要一個對 const 的參考,因為 rvalues 可以 系結到常量參考),這使得復制成為了在移動不可用時的后備手段。
因此,每當你使用std::move時,你最終會呼叫一個復制建構式。這就解釋了為什么與第 3 版(分別是第 1 版)相比,第 4 版(分別是第 2 版)有一個對復制建構式的額外呼叫。
讓我們來看看其余的復制建構式。
正如你所正確指出的,復制建構式是通過std::bind的第二個引數來呼叫的。在所有的版本中,這都是第一次呼叫。
當你宣告時
std::function<void ()> callable = std::bind(& NAME::f, n)。
你正在呼叫std::function的建構式,傳遞一個引數std::bind(&NAME::f, n),然后,再次復制。這說明了版本1中復制建構式的第二次呼叫,以及版本2中的第三次呼叫。請注意,強制性的復制消除在這里并不適用,因為你沒有傳遞一個std::function物件。
最后,當你使用
auto callable = std::bind(..)
你正在宣告一個未命名型別的變數,它包含了對std::bind的呼叫結果。
在宣告中沒有涉及復制。這就是為什么與第一版相比,第三版少了一個對復制建構式的呼叫。
對其他問題的回答
1.
callable_1和callable_2的型別不同。callable_2是一個std::function物件,而callable_1是一個未指定的型別,是std::bind的結果。另外,它不是一個lamda。要看到這一點,你可以運行類似
auto callable_1 = std::bind(&NAME::f, n)。
std::function<void ()> callable_2 = std::bind(&NAME::f, n)。
//一個通用的lambda。
auto callable_3 = [&]() { n.f(); };
std::cout << std::boolalpha;
std::cout << std::is_bind_expression<decltype(callable_1)>:value << std::endl。
std::cout << std::is_bind_expression<decltype(callable_2)> ::value << std::endl。
std::cout << std::is_bind_expression<decltype(callable_3)> ::value << std::endl。
看到它在Coliru上直播.
。2.
正如@RemyLebeau所指出的,對std::bind的參考中的注釋的嚴格解釋,
正如在Callable中所描述的,當呼叫一個指向非靜態成員的指標 函式的指標或非靜態資料成員的指標時,第一個引數必須是一個參考或指標。 必須是一個參考或指標(可能包括智能指標,例如 包括智能指標,如 std::shared_ptr 和 std::unique_ptr),該物件的成員將被訪問。
這將表明該代碼必須用&n來呼叫,而用n來呼叫將是非法的。
然而,呼叫operator()的結果是std::invoke。從std::invoke的參考中,我們讀到(我做了一點重新編排):
如果f是一個指向T類的成員函式的指標:
a) 如果std::is_base_of<T, std::decay_t<decltype(t1)>>:value為真,那么INVOKE(f, t1, t2, ..., tN)就相當于(t1.*f)(t2。 ..., tN)
b) 如果std::decay_t<decltype(t1)>是一個專業化的 std::reference_wrapper,那么INVOKE(f, t1, t2, ..., tN)等同于 到(t1.get().*f)(t2, ..., tN)
。c) 如果t1不滿足前面的 項,那么 INVOKE(f, t1, t2, ..., tN) 等于 ((*t1).*f)(t2, ..., tN)。
根據這一點,用 3. 注意, 除了這個問題,如果你使用 系結的引數是復制或移動的,并且永遠不會通過
除非用 std::ref 或 std::cref 包裝,否則絕不會以參考的方式傳遞。
就個人而言,我發現 lambda 語法更加清晰。
4. 從的 因此,使用占位符的效果又回到了問題1的情況。該代碼確實用不同的編譯器編譯。 uj5u.com熱心網友回復: 注意。
你也可以用lambda來代替bind uj5u.com熱心網友回復: 現在你可以從lambdas中制作std::function<RetvalType(Args...)> 像這樣
標籤:n(情況a))或&n(情況c))呼叫std::bind應該是等價的(除了如果你使用n的額外拷貝),因為std: :decay_t<decltype(n)>給出了NAME和std::is_base_of<NAME, NAME>:value是true(見 std::is_base_of的參考)。)
傳遞ref(n)對應于b)的情況,所以它同樣應該是正確的,并且等同于其他情況(除了上面討論的副本之外)。
cref為你提供了一個指向const NAME&的參考包裝器。所以你將無法呼叫callable,因為NAME::f不是一個const成員函式。事實上,如果你添加一個callable();,代碼就不會被編譯。std::ref或者NAME::f是const,我沒有看到auto callable = [&n]{n.f()};和auto callable = std::bind(&NAME::f, ref(n));之間有根本區別。關于這些,請注意那個:
std::bind的參考資料中,在operator()下,我們讀到如果存盤的引數arg是T型別的,對于它來說
std::is_placeholder::value != 0(意思是,一個占位符,如
std::placeholder::_1, _2, _3, ...被用作初始呼叫系結的引數。
系結的初始呼叫),那么占位符所表示的引數
(u1代表_1,u2代表_2,等等)被傳遞給可呼叫物件:std::占位符中的
上面的std::invoke呼叫中的引數vn是std::forward(uj),并且
在同一呼叫中的相應型別Vn是Uj&&。
//默認建構式將被呼叫。
名稱n。
//bind將n復制到一個內部結構中,n的復制建構式被呼叫。
auto fn = std::bind(&NAME::f, n)。
//賦值運算子將fn的成員復制到std::function的成員。
//這也是NAME的一個拷貝建構式呼叫。
std::function<void()> callable = fn;
std:: function<void ()> callable([&n]{n. f()})。
#pragma once
namespace details
{
template<typename T>
結構 記憶體型別
{
using type = void;
};
template<typename RetvalType, typenameClassType, typename. Args>
struct memfun_type<RetvalType(ClassType::*)(Args...) const>
{
using type = typename std::function<RetvalType(Args...)> 。
};
} //詳情
template<typename Fn>
typename details::memfun_type<decltype(&Fn::operator())> ::type
make_std_function(Fn const& fn)
{
return fn。
}
auto std_fn = make_std_function([](int n){return 2*n; });
int answer = std_fn(2)。
