[英]Aggregate reference member and temporary lifetime
鑒於此代碼示例,有關傳遞給S
的臨時字符串的生命周期的規則是什么。
struct S
{
// [1] S(const std::string& str) : str_{str} {}
// [2] S(S&& other) : str_{std::move(other).str} {}
const std::string& str_;
};
S a{"foo"}; // direct-initialization
auto b = S{"bar"}; // copy-initialization with rvalue
std::string foobar{"foobar"};
auto c = S{foobar}; // copy-initialization with lvalue
const std::string& baz = "baz";
auto d = S{baz}; // copy-initialization with lvalue-ref to temporary
根據標准:
N4140 12.2 p5.1(在N4296中刪除)
綁定到構造函數的ctor-initializer(12.6.2)中的引用成員的臨時綁定將持續存在,直到構造函數退出。
N4296 12.6.2 p8
綁定到mem-initializer中的引用成員的臨時表達式格式不正確。
因此,擁有像[1]
這樣的用戶定義構造函數肯定不是我們想要的。 它甚至應該在最新的C ++ 14中形成不良(或者是它?)gcc和clang都沒有警告它。
它是否隨直接聚合初始化而改變? 在這種情況下,我看起來,臨時壽命延長了。
現在關於復制初始化, 默認移動構造函數和引用成員聲明隱式生成[2]
。 鑒於移動可能被省略,同樣的規則是否適用於隱式生成的移動構造函數?
哪個a, b, c, d
有一個有效的參考?
除非存在特定異常,否則將擴展綁定到引用的臨時對象的生存期。 也就是說,如果沒有這樣的例外,那么壽命將會延長。
從最近的草案,N4567:
第二個上下文[延長生命周期]是指引用綁定到臨時的。 綁定引用的臨時對象或綁定引用的子對象的完整對象的臨時對象在引用的生命周期內持續存在,除了:
- (5.1)綁定到函數調用(5.2.2)中的引用參數的臨時對象將持續到包含該調用的完整表達式完成為止。
- (5.2)函數返回語句(6.6.3)中返回值的臨時綁定的生存期不會延長; 臨時在return語句中的full-expression結束時被銷毀。
- (5.3)在new-initializer(5.3.4)中對引用的臨時綁定一直持續到包含new-initializer的full-expression完成為止。
正如OP所提到的,對C ++ 11的唯一重大改變是,在C ++ 11中,引用類型的數據成員(來自N3337)還有一個例外:
- 綁定到構造函數的ctor-initializer(12.6.2)中的引用成員的臨時綁定將持續存在,直到構造函數退出。
這在CWG 1696 (后C ++ 14)中被刪除,並且通過mem-initializer將臨時對象綁定到引用數據成員現在是不正確的。
關於OP的例子:
struct S { const std::string& str_; }; S a{"foo"}; // direct-initialization
這將創建一個臨時的std::string
並使用它初始化str_
data成員。 S a{"foo"}
使用聚合初始化,因此不涉及mem-initializer。 生命周期擴展的例外都不適用,因此該臨時的生命周期延長到參考數據成員str_
的生命周期。
auto b = S{"bar"}; // copy-initialization with rvalue
在使用C ++強制復制省略之前17:正式地,我們創建一個臨時的std::string
,通過將臨時std::string
綁定到str_
reference成員來初始化臨時S
然后,我們將臨時S
移動到b
。 這將“復制”引用,這不會延長std::string
臨時的生命周期。 但是,實現將忽略從臨時S
到b
的移動。 但這不得影響臨時std::string
的生命周期。 您可以在以下程序中觀察到這一點:
#include <iostream>
#define PRINT_FUNC() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
struct loud
{
loud() PRINT_FUNC()
loud(loud const&) PRINT_FUNC()
loud(loud&&) PRINT_FUNC()
~loud() PRINT_FUNC()
};
struct aggr
{
loud const& l;
~aggr() PRINT_FUNC()
};
int main() {
auto x = aggr{loud{}};
std::cout << "end of main\n";
(void)x;
}
請注意,在“主要結束”之前調用loud
析構函數,而x
直到追蹤之后才會生效。 正式地,臨時loud
在創建它的全表達結束時被破壞。
如果aggr
的移動構造aggr
是用戶定義的,則行為不會更改。
在C ++ 17中使用強制copy-elision:我們使用lhs b
上的對象識別rhs S{"bar"}
上的對象。 這導致臨時的壽命延長到b
的壽命。 見CWG 1697 。
對於其余兩個示例,移動構造函數(如果調用)只是復制引用。 當然,移動構造函數( S
)可以省略,但這是不可觀察的,因為它只復制引用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.